システム・プログラム 電子・情報工学系 新城 靖 <yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/classes/syspro-2003/2003-06-23
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~yas/
http://www.is.tsukuba.ac.jp/~yas/index-j.html
プロセスはインタプリタの実行形式(機械語)から作られ、インタプリタのソー ス・プログラムは、そのプロセスが読み込む単なるデータとなる。
Unix には、スクリプトを簡単に実行する仕組みとして #!がある。
実行する。---------------------------------------------------------------------- % cat > run-cat#!/bin/cat hello ^D % chmod +x run-cat
% ls -l run-cat
-rwxr-xr-x 1 yas 20 Sep 13 04:11 run-cat %
----------------------------------------------------------------------
これは次のように実行したものと同じになる。---------------------------------------------------------------------- % ./run-cat#!/bin/cat hello %
----------------------------------------------------------------------
---------------------------------------------------------------------- % /bin/cat ./run-cat#!/bin/cat hello %
----------------------------------------------------------------------
インタプリタの実行形式の絶対パス名: /dir/interpreter
そのソース・プログラムのファイル名: run
run
の内容:
---------------------------------------------------------------------- #!/dir/interpreter arg-1 arg-2 arg-3 <以下、プログラム> ----------------------------------------------------------------------このファイルに実行可能属性を付ける(
chmod +x
)と、
ファイル名を入力して実行することができる。
これは、次のようにインタプリタを起動したものと同じ結果になる。---------------------------------------------------------------------- % chmod +x ./run% ./run arg-a arg-b arg-c
<実行結果> ----------------------------------------------------------------------
---------------------------------------------------------------------- % /dir/interpreter ./run 'arg-1 arg-2 arg-3' arg-a arg-b argc----------------------------------------------------------------------
./run
ファイルに書いた引数は、シェルから実行する時場合には、
''で囲まれた時と同じような形式でインタプリタのプロ
セスへの引数として渡される。
#!
」行は、Unixのカーネルが解釈する。
シェルが、ファイルの先頭を読み、指定されたインタプリタを起動するのでは
ない。「#!
」行では、シェル変数、環境変数、エイリアスはつ
かえない。
スクリプト言語のプログラムでは
「#
」から始まる行がコメントであると都合がよい。
awk
や sed
のように、プログラムが含まれたファイルを指定
する時に -f
(program file) オプションが必要なものは、次の
ように、この行に -f
を付ける。
---------------------------------------------------------------------- #!/bin/awk -f { print } ----------------------------------------------------------------------
シェル・スクリプトの先頭の
#!/bin/sh
や
#!/bin/csh
は、
そのインタプリタを起動するという意味である。
tcsh は、csh に terminal での編集機能や補完機能を付けたもの。シェル・ スクリプトを書く時には、多くのシステムで備わっている /bin/csh を使うこ とが多い。
---------------------------------------------------------------------- % gcc -I/usr/local/include/ file1.c -llib1 -llib2 -o progtcsh の機能で、^p (Control+P) で1行戻して、echo でファイルに落とす。 「|」があれば、'' でくくる。csh の引数にファイル名を与えて実行できる。(^p で1行もどす。^a で、行頭に移動して echo と打ち、^e して > run と打つ) % echo gcc -I/usr/local/include/ file1.c -llib1 -llib2 -o prog > run
% csh run
%
----------------------------------------------------------------------
いちいち csh と打たないでもいいようにするには、chmod する。
---------------------------------------------------------------------- % chmod +x run#! がなければ、execve() システム・コールがエラーになる。その場合、tcsh が sh か csh (先頭が#の時)を実行する。% ./run
%
----------------------------------------------------------------------
必要なら、エディタで「#!/bin/csh」か「#!/bin/sh」を入れる。
数行にわたるものの場合、history コマンドを使う。
---------------------------------------------------------------------- % history% history | tail -5 > run
% emacs run
----------------------------------------------------------------------
「#!/bin/csh -f」と、-f を付けた方が、~/.cshrc を読み込まないので起動 が速い。ただし、~/.cshrc での設定(aliasなど)は効かないことがある。環境 変数は、今の状態が引き継がれる(~/.cshrc を読み込ませない方が都合がよい ことが多い)。
% csh -f -x run![]()
-n オプションを付けて実行して、構文のチェックだけ行う。
シェルに1行ずつ与えて実行してみる。
強制的に縦に並べるオプション ls -1 や横に並べるオプション ls -C もある。---------------------------------------------------------------------- % lsfile1 file2 file3 % ls | cat
file1 file2 file3 %
----------------------------------------------------------------------
UTMP_FILE
」
または、「WTMP_FILE
」という文字列を含む行を、ファイル
/usr/include/utmp.h
から探し、結果を表示している。
一般的には、次のようになる。---------------------------------------------------------------------- % egrep '[UW]TMP_FILE' /usr/include/utmp.h#define UTMP_FILE "/var/adm/utmp" #define WTMP_FILE "/var/adm/wtmp" %
----------------------------------------------------------------------
% egrep pattern file1 file2 ...![]()
pattern
としては、次のような正規表現(regular
expressions)が使える。
-v オプションを付けると、マッチしなかった行が表示される。
-s オプションを付けると、パタンを含むか含まないかの判定だけを行い、画 面に結果を表示しない。シェル・スクリプトの if 文の中で使う。
「*」、「$」、「[]」など、シェルが展開してしまう文字をパタンとして指定 する時には、シングル・クォート「''」で括る。
egrep は、機能が拡張され、アルゴリズムも改良されていて速い。
~/.cshrc
などを設定して、~/bin
をpath シェル変数(PATH
環境変数)に含まれるようにすることを奨める。そして自分で作成したプログ
ラムやスクリプトを、~/bin
に置くと./
などで実行する必
要はない。
ただし、ファイルを作成し、chmod +x した後で、1度だけ rehash コマンド
を打つ必要がある。
chmod も rehash も、シェルごとに1度だけやればよい。端末をたくさん開い ていた時には、作成したスクリプトをすぐに使いたい時にはそれぞれのシェル でrehash コマンドを実行する。% emacs ~/bin/newcommand% chmod +x ~/bin/newcommand
% rehash
% newcommand
% emacs ~/bin/newcommand
% newcommand
% emacs ~/bin/newcommand
% newcommand
%
![]()
rehash の意味は、ハッシュ表を作り直すことである。path にあるコマンドは、 csh は、コマンドを打つたびに探すのではなくてハッシュ表に入れてそれを検 索している。
rehash は、新しいシェルが実行される時には自動的に行われている。次にロ グインした時、chmod +x した後に開いた端末ではrehash を実行する必要はな い。
GTK+ は、もともとは X ウインドウ上の widget セットとして作られた。 widget とは、ボタンやテキストボックスやスクロールバーななど部品を意味 する。X ウインドウでよく使われる widgetセットやライブラリには、次のよ うなものがある。
GTK+ は、C言語で書かれているが、C言語以外の言語からも使えるようにな つている。Ruby、Perl、Python のようなスクリプト言語からも使える。
GTK+ は、Windows 用, MacOSX用 も存在する。
---------------------------------------------------------------------- 1: 2: /* 3: gtk-hello.c -- GTK+を使ってウインドウに文字列を表示するプログラム 4: ~yas/syspro/window/gtk-hello.c 5: Start: 2003/06/21 22:57:52 6: */ 7: 8: #include <gtk/gtk.h> 9: 10: void my_delete_event() 11: { 12: printf("my_delete_event\n"); 13: gtk_main_quit(); 14: } 15: 16: main( int argc, char *argv[] ) 17: { 18: GtkWidget *window; 19: GtkWidget *label1; 20: 21: gtk_set_locale(); 22: gtk_init( &argc, &argv ); 23: window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); 24: gtk_signal_connect( GTK_OBJECT(window), "delete_event", 25: GTK_SIGNAL_FUNC(my_delete_event), NULL ); 26: gtk_signal_connect( GTK_OBJECT(window), "destroy", 27: GTK_SIGNAL_FUNC(my_delete_event), NULL ); 28: gtk_container_set_border_width( GTK_CONTAINER(window), 50); 29: 30: label1 = gtk_label_new("Hello,world!"); 31: gtk_container_add( GTK_CONTAINER(window),label1 ); 32: 33: gtk_widget_show( label1 ); 34: gtk_widget_show( window ); 35: gtk_main(); 36: printf("return from gtk_main()\n"); 37: exit( 0 ); 38: } ----------------------------------------------------------------------
GTK+ のプログラムでは、ヘッダファイル gtk/gtk.h
を読み込む。
my_delete_event() は、(トップレベルのウインドウが) "delete_event"、ま たは、"destroy" というイベントを受け取った時に実行される関数である。 イベントとは、何かが生じた(ボタンが押された、キーボードが打たれた、マ ウスがウインドウに入った)ことの知らせである。"delete_event" や "destroy" は、プログラムが終了するようにというイベントである。 GTK+ では、イベントのことを「シグナル」と呼んでいる。
gtk_set_locale() や gtk_init() は、GTK+のプログラムを作成する時の定石 (決り文句)である。gtk_rc_parse() でアプリケーションごとの設定ファイ ルを読み込むこともある。
main() では、ウインドウ(トップレベル)を1つ作っている。内部にボタンを 作ると、ボタンも1つのウインドウになる。一番外側のウインドウがトップレ ベルのウインドウである。
ウインドウに何か文字を表示するには、まず、ラベルと呼ばれる GtkWidget を作る。それを表示したいウインドウに gtk_container_add() で張り付ける。 ウインドウの種類の中でも、ラベルを張り付けられるのは、コンテナ (container) の一種(サブクラス)だけである。
作成した widget (ウインドウ)は、show することで表示されるようになる。 show を忘れると表示されない。
gtk_main() は、GTK+のイベント・ループである。 イベント・ループは、次のような操作を行う。
gtk/gtk.h
等の位置を指
定する。さらに、-l で必要なライブラリ(libgtk-1.2 や libX11)をリンクす
る。-I や -l で何を指定すればよいかは、gtk-config というコマンドを実行
するとわかる。
---------------------------------------------------------------------- % cp ~yas/syspro/window/gtk-hello.c .% cp ~yas/syspro/window/Makefile .
% make -n gtk-hello
cc `gtk-config --cflags` -o gtk-hello gtk-hello.c `gtk-config --libs` % gtk-config --cflags
-I/usr/include/gtk-1.2 -I/usr/include/glib-1.2 -I/usr/lib/glib/include -I/usr/X11R6/include % gtk-config --libs
-L/usr/lib -L/usr/X11R6/lib -lgtk -lgdk -rdynamic -lgmodule -lglib -ldl -lXi -lXext -lX11 -lm % make gtk-hello
cc `gtk-config --cflags` -o gtk-hello gtk-hello.c `gtk-config --libs` % ./gtk-hello
^C %
----------------------------------------------------------------------
図? gtk-hello の実行結果のウインドウ
---------------------------------------------------------------------- 1: 2: /* 3: gtk-button3.c -- GTK+を使ってウインドウにボタンを表示するプログラム 4: ~yas/syspro/window/gtk-button3.c 5: Start: 2003/06/21 22:57:52 6: */ 7: 8: #include <gtk/gtk.h> 9: 10: void my_delete_event() 11: { 12: printf("my_delete_event\n"); 13: gtk_main_quit(); 14: } 15: 16: void button3_clicked(GtkWidget *w, gpointer *data) 17: { 18: printf("button3_clicked():\n"); 19: gtk_main_quit(); 20: } 21: 22: main( int argc, char *argv[] ) 23: { 24: GtkWidget *window; 25: GtkWidget *vbox; 26: GtkWidget *button1, *button2, *button3 ; 27: 28: gtk_set_locale(); 29: gtk_init( &argc, &argv ); 30: window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); 31: gtk_signal_connect( GTK_OBJECT(window), "delete_event", 32: GTK_SIGNAL_FUNC(my_delete_event), NULL ); 33: gtk_signal_connect( GTK_OBJECT(window), "destroy", 34: GTK_SIGNAL_FUNC(my_delete_event), NULL ); 35: gtk_container_set_border_width( GTK_CONTAINER(window), 50); 36: 37: vbox = gtk_vbox_new( FALSE, 0 ); 38: 39: button1 = gtk_button_new_with_label("kterm"); 40: gtk_box_pack_start( GTK_BOX(vbox), button1, FALSE, FALSE, 0); 41: gtk_widget_show( button1 ); 42: 43: button2 = gtk_button_new_with_label("netscape"); 44: gtk_box_pack_start( GTK_BOX(vbox), button2, FALSE, FALSE, 0); 45: gtk_widget_show( button2 ); 46: 47: button3 = gtk_button_new_with_label("quit"); 48: gtk_box_pack_start( GTK_BOX(vbox), button3, FALSE, FALSE, 0); 49: gtk_widget_show( button3 ); 50: gtk_signal_connect( GTK_OBJECT(button3), "clicked", 51: GTK_SIGNAL_FUNC(button3_clicked), NULL ); 52: 53: gtk_container_add( GTK_CONTAINER(window), vbox); 54: 55: gtk_widget_show( vbox ); 56: gtk_widget_show( window ); 57: 58: gtk_main(); 59: printf("return from gtk_main()\n"); 60: exit( 0 ); 61: } ----------------------------------------------------------------------
button3_clicked() は、main() で作成する button3 が "clicked" というイ ベントを受け取った時に実行される関数である。gtk_main_quit() を呼び出し て終了している。
main() で gtk_set_locale() から gtk_container_set_border_width() までは、 gtk-hello.cと同じである。
vbox は、縦(vertical)に widget を並べるための箱である。hbox という、横 (horizontal)に並べる箱もある。
gtk_button_new_with_label() は、ラベルを張り込んだボタンを作る関数であ る。まずボタンを作り、後でラベルを張り込む方法もある。作成したボタン button1, button2, button3 をvbox の中に gtk_box_pack_start() で埋め込 んでいる。show もしている。
button3 については、"clicked" というイベントを受け取った時に button3_clicked() という関数が呼ばれるように設定している。
vbox と window を、show してイベント・ループ gtk_main() を呼んでいる。
実行例:
---------------------------------------------------------------------- % cp ~yas/syspro/window/gtk-button3.c .% cp ~yas/syspro/window/Makefile .
% make gtk-button3
cc `gtk-config --cflags` -o gtk-button3 gtk-button3.c `gtk-config --libs` % ./gtk-button3
button3_clicked(): return from gtk_main() %
----------------------------------------------------------------------
図? gtk-button3 の実行結果のウインドウ
---------------------------------------------------------------------- 1: #!/usr/bin/ruby 2: # gtk-hello.rb -- GTK+を使ってウインドウに文字列を表示するプログラム(Ruby版) 3: # ~yas/syspro/window/gtk-hello.rb 4: # Start: 2003/06/21 23:45:48 5: 6: require 'gtk' 7: 8: def my_delete_event() 9: printf("my_delete_event()\n") 10: Gtk::main_quit() 11: end 12: 13: def main() 14: window = Gtk::Window.new(Gtk::WINDOW_TOPLEVEL) 15: window.signal_connect("delete_event") { my_delete_event() } 16: window.signal_connect("destroy") { my_delete_event() } 17: window.border_width = 50 18: 19: label1 = Gtk::Label.new("hello,world") 20: window.add( label1 ) 21: label1.show() 22: window.show() 23: Gtk.main() 24: printf("return from Gtk.main()\n") 25: end 26: 27: main() ----------------------------------------------------------------------
Ruby/GTK+ のプログラムでは、ライブラリgtk
を require する。
これにより、Gtk:: で始まる定数やメソッドが使えるようになる。
my_delete_event() は、(トップレベルのウインドウが) "delete_event"、ま たは、"destroy" というイベントを受け取った時に実行される関数である。 Ruby の関数定義は、def 関数名(引数..) で始まり、end で終わる。
main() は定義しない方が普通である。定義する場合は、最後に main() を呼 び出すことを忘れないようにする。
内部の処理は、C言語版とほとんど同じである。
Ruby では、変数(小文字で始まる)は、宣言しないで使ってもよい。型はない。 ローカル変数は、def と end の間で有効である。その外に、インスタンス変 数(@がついたもの)や暮す変数(@@がついたもの)がある。大文字で始まると定 数になる。
Ruby では、v.func で、変数 v が指しているオブジェクトで定義された関数 func の呼出しになる。v をつけないで単に func とすると、同じクラス内の 関数を呼ぶことにる。後ろの () はつけなくてもよい。
window.signal_connec() では、イベントが生じると{ } の中のブロックが実 行される。この中に処理を書くといちいち関数を定義しなくてもよい。
実行例:
---------------------------------------------------------------------- % cp ~yas/syspro/window/gtk-hello.rb .% ls -l gtk-hello.rb
-rwxr-xr-x 1 yas lab 603 6月 23 01:40 gtk-hello.rb % chmod +x gtk-hello.rb
% ls -l gtk-hello.rb
-rwxr-xr-x 1 yas lab 603 6月 23 01:40 gtk-hello.rb % ./gtk-hello.rb
./gtk-hello.rb:23:in `main': Interrupt from ./gtk-hello.rb:23:in `main' from ./gtk-hello.rb:27 %
----------------------------------------------------------------------
window.show_all() は、1つひとつ show する代わりに全部まとめて show している。~yas/syspro/window/gtk-button3.rb
---------------------------------------------------------------------- % ls-c ~/syspro-2003/file/ヒント:ls の結果から、egrep で .c や .h で終わるものを抜き出す。ある いは、シェルのパタン・マッチの機能を使って、*.c や *.h だけを取り出す。fd-print.c file-copy.c mmap-head.c stdio-thru.c utmp-print.c wtmp-last10.c ystat.c ystat.h ----------------------------------------------------------------------
余裕があれば、-l などのオプションが付けられるようにしなさい。
---------------------------------------------------------------------- % ls-dot ~ヒント:ls に -a オプションを付けると、全てのファイルを表示する。 先頭が「.」のものを抜き出す。. .. .cshrc .emacs .login %
----------------------------------------------------------------------
csh, tcsh, sh で 「.*」と指定して探す方法もある。
余裕があれば、-l などのオプションが付けられるようにしなさい。
---------------------------------------------------------------------- % ls-dir ~ヒント:ls -l で、d から始まる行だけを抜き出す。あるいは、csh の -d や test の -d でディレクトリだけを選びだす。Mail lib syspro tmp %
----------------------------------------------------------------------
できるだけ上のようにディレクトリの名前だけ表示するようにしなさい。 モードや日付は表示しないようにしなさい。
余裕があれば、-l などのオプションが付けられるようにしなさい。
---------------------------------------------------------------------- % ls-size ~yas/syspro/windowヒント:ls -l の出力を sort コマンドでソートする。 ソートは、第5フィールド(-k5)で行う。-rwxr-xr-x 1 yas lab 17045 6月 23 00:48 gtk-button3 -rwxr-xr-x 1 yas lab 16166 6月 23 00:00 gtk-hello drwxr-xr-x 2 yas lab 4096 6月 23 01:25 CVS drwx------ 2 yas lab 4096 6月 23 01:15 tmp -rw-r--r-- 1 yas lab 1564 6月 23 00:48 gtk-button3.c -rwxr-xr-x 1 yas lab 910 6月 23 01:14 gtk-button3.rb -rw-r--r-- 1 yas lab 895 6月 23 00:05 gtk-hello.c -rwxr-xr-x 1 yas lab 618 6月 23 01:14 gtk-hello.rb -rw-r--r-- 1 yas lab 200 6月 23 01:25 Makefile 合計 64 %
----------------------------------------------------------------------
---------------------------------------------------------------------- % wc-lines *.cこの課題では、合計は表示されなくてもよい。73 181 1166 pipe-rw-dup.c 65 155 1015 pipe-rw-nodup.c 57 161 1217 proc-uid-print.c 50 98 793 setjmp-longjmp.c 39 100 920 signal-int.c 38 112 842 proc-create.c 21 49 483 exec-date.c 20 50 419 cont-1.c 20 50 419 cont-0.c 16 52 447 env-print.c 15 57 460 arg-print.c 14 24 267 fork-hello.c 12 32 300 cont-2.c %
----------------------------------------------------------------------
ヒント:wc の出力を sort コマンドでソートする。foreach か for で1つず つ wc コマンドを実行して、全体の結果を sort するか、引数 $* で wc した 後、sort して、合計の行を削る。
---------------------------------------------------------------------- % ps -leそのうち、メモリのサイズ(SZ)が大きいプロセスを 10 個だけ表示するシェル・ スクリプトを作りなさい。F S UID PID PPID C PRI NI P SZ:RSS WCHAN TTY TIME CMD 39 S 0 0 0 0 39 RT * 0:0 80268270 ? 0:40 sched b0 S 0 1 0 0 39 20 * 98:42 8027eb80 ? 0:15 init 119 S 0 2 0 0 39 RT * 0:0 80267c30 ? 2:06 vhand 119 S 0 3 0 0 39 RT * 0:0 802678a8 ? 1:09 bdflush ... b0 S 0 11771 684 0 60 20 * 11480:1622 8053853c ? 22:26 Xsgi b0 S 0 22729 684 0 60 20 * 862:140 80538474 ? 0:00 xdm 90 Z 1231 24304 24302 0 0 - - - - - - 0:00 <defunct> %
----------------------------------------------------------------------
---------------------------------------------------------------------- % ps-sz-top10ヒント:SZ の順(第10フィールド)に sort して head する。b0 S 0 11771 684 0 60 20 * 11480:1622 8053853c ? 22:26 Xsgi b0 S 0 548 547 0 60 20 * 2678:1619 805384e0 ? 0:30 jserver b0 S 1231 24145 23879 0 60 20 * 2114:909 80538494 pts/1 0:01 emacs b0 S 0 24250 23879 0 60 20 * 1166:653 80538538 pts/1 0:01 kterm b0 S 40508 24517 24509 0 28 20 * 1044:766 8027f5a0 pts/3 0:00 mnews b0 S 0 22729 684 0 60 20 * 862:140 80538474 ? 0:00 xdm b0 S 0 684 1 0 60 20 * 854:75 80538500 ? 0:01 xdm b0 S 40508 24509 24507 0 39 20 * 740:336 8027eb80 pts/3 0:01 tcsh b0 S 1231 24251 24250 1 39 20 * 704:437 8027eb80 pts/2 0:01 tcsh b0 S 1231 23879 23876 0 39 20 * 703:321 8027eb80 pts/1 0:01 tcsh %
----------------------------------------------------------------------
余裕があれば、RSS の順、CPU 時間の順に表示するスクリプトを作りなさい。
類似のことを実行するプログラムとして top がある。
% diff-backup *.cヒント:foreach または for 文で、引数のファイルについて、 "$file"~ のような名前のファイルが存在するかを調べる。 存在すれば、diff コマンドで表示する。%
![]()
余裕があれば、比較しているファイルの名前を表示したり、ファイルごとに停 止する、引数を取る、diff に対するオプションを取る、などの工夫をしなさ い。
% mv-lower [A-Z]*![]()
ヒント:ファイル名を echo して、tr で小文字にして、それを `` でシェル変数に入れる。元の名前から小文 字の名前に mv で変える。
余裕があれば、大文字と小文字を変換することで、ファイルが上書きされる時 には警告を出したり、ユーザに問い合わせたりするようにしなさい。
% find-pattern printf *.cヒント:egrep -s で調べて、含んでいた時だけファイル名を表示する。arg-print.c env-print.c signal-int.c %
![]()
% countdownヒント:foreach か for のリストに数を並べる。 sleep 1 で、1ごとに止める。5 4 3 2 1 0 %
![]()
余裕があれば、引数で何秒数えるかを指定できるようにしなさい。
% countdown 33 2 1 0 %
![]()
% show-n-m 10 20 filenameヒント:head コマンドと tail コマンドを組み合わせて使う。 head も tail も、表示する行数を与えることができる。![]()
余裕があれば、-n オプションを付けなさい。これは、ファイルに行番号を振 るものである。nl コマンドを使うとよい。(cat -n が使えるシステムもある。)
ヒント:
余裕があれば、ボタンのラベルや実行すべきコマンドをファイルから読み込ん で定義できるようにしなさい。
3回、^C (Control-C) が押されたら終了するプログラムは、 signal-int.cにある。
余裕があれば、引数を取るようにしなさい。また、カウント中の文字をボタン に表示するようにしなさい。