筑波大学 システム情報工学研究科 
                                       コンピュータサイエンス専攻, 電子・情報工学系
                                       新城 靖
                                       <yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
	http://www.coins.tsukuba.ac.jp/~syspro/2008/No10.html
あるいは、次のページから手繰っていくこともできます。
	http://www.coins.tsukuba.ac.jp/~syspro/2008/
	http://www.coins.tsukuba.ac.jp/~yas/
f() {
    char buf[BUFFERSIZE];
    snprintf(buf,sizeof(buf),"hello");
    return( buf ); // 誤り
}
ローカル変数用のメモリは、関数から return したら、別の関数が利用する。
main() {
       f();
       g();
}
図? ローカル変数用のメモリ
関数から文字列を return する方法
f() {
    auto int x;// auto は省略可能。
    ...
}
前半の「バッファオーバーフロー」の説明参照。
プロセスはインタプリタの実行形式(機械語)から作られ、インタプリタのソー ス・プログラムは、そのプロセスが読み込む単なるデータとなる。
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
% 
「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 prog 
(^p で1行もどす。^a で、行頭に移動して echo と打ち、^e して > run と打つ)
% echo gcc -I/usr/local/include/ file1.c -llib1 -llib2 -o prog > run 
% csh run 
% 
tcsh の機能で、^p (Control+P) で1行戻して、echo でファイルに落とす。
「|」があれば、'' でくくる。csh の引数にファイル名を与えて実行できる。
いちいち csh と打たないでもいいようにするには、chmod する。
% chmod +x run 
% ./run 
% 
#! がなければ、execve() システム・コールがエラーになる。その場合、tcsh 
が sh か csh (先頭が#の時)を実行する。
必要なら、エディタで「#!/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行ずつ与えて実行してみる。
~/.cshrcなどを設定して、~/bin をpath シェル変数(PATH 
環境変数)に含まれるようにすることを奨める。そして自分で作成したプログ
ラムやスクリプトを、~/bin に置くと./ などで実行する必
要はない。
ただし、ファイルを作成し、chmod +x した後で、1度だけ rehash コマンド
を打つ必要がある。
% emacs ~/bin/newcommand 
% chmod +x ~/bin/newcommand 
% rehash 
% newcommand 
% emacs ~/bin/newcommand 
% newcommand 
% emacs ~/bin/newcommand 
% newcommand 
% 
chmod も rehash も、シェルごとに1度だけやればよい。端末をたくさん開い
ていた時には、作成したスクリプトをすぐに使いたい時にはそれぞれのシェル
でrehash コマンドを実行する。
rehash の意味は、ハッシュ表を作り直すことである。path にあるコマンドは、 csh は、コマンドを打つたびに探すのではなくてハッシュ表に入れてそれを検 索している。
rehash は、新しいシェルが実行される時には自動的に行われている。次にロ グインした時、chmod +x した後に開いた端末ではrehash を実行する必要はな い。
% ls 
file1  file2  file3
% ls | cat 
file1
file2
file3
% 
強制的に縦に並べるオプション ls -1 や横に並べるオプション ls -C もある。
UTMP_FILE」
または、「WTMP_FILE」という文字列を含む行を、ファイル 
/usr/include/utmp.h から探し、結果を表示している。
% egrep '_PATH_[UW]TMP' /usr/include/utmp.h 
#define _PATH_UTMP      "/var/run/utmp"
#define _PATH_WTMP      "/var/log/wtmp"
% 
一般的には、次のようになる。
% egrep pattern file1 file2 ... 
pattern としては、次のような正規表現(regular
expressions)が使える。
-v オプションを付けると、マッチしなかった行が表示される。
-s オプションを付けると、パタンを含むか含まないかの判定だけを行い、画 面に結果を表示しない。シェル・スクリプトの if 文の中で使う。
「*」、「$」、「[]」など、シェルが展開してしまう文字をパタンとして指定 する時には、シングル・クォート「' '」で括る。
egrep は、機能が拡張され、アルゴリズムも改良されていて速い。
   1:	#!/usr/bin/ruby
   2:	# cgi-printarg-ruby.cgi -- CGI プログラムに対する引数を表示するプログラム
   3:	# ~yas/syspro/www/cgi-printarg-ruby.cgi
   4:	# Created on 2005/06/27 01:52:36
   5:	
   6:	require "cgi"
   7:	
   8:	def main()
   9:	        $SAFE = 1
  10:	        @cgi = CGI.new()
  11:	        print_header()
  12:	        print_content()
  13:	        exit( 0 )
  14:	end
  15:	
  16:	def print_header()
  17:	        printf("Content-Type: text/html\n")
  18:	        printf("\n")
  19:	end
  20:	
  21:	def print_content()
  22:	        printf("<HTML><HEAD></HEAD><BODY><PRE>\n")
  23:	        printf("request_method: %s\n",e(@cgi.request_method))
  24:	        printf("script_name: %s\n",e(@cgi.script_name))
  25:	        printf("query_string: %s\n",e(@cgi.query_string))
  26:	        printf("content_length: %d\n",@cgi.content_length)
  27:	        qh = @cgi.keys
  28:	        i = 0
  29:	        qh.each { |name|
  30:	            val = @cgi[name]
  31:	            printf("qv[%d]: %s=%s \n",i,e(name),e(val) )
  32:	            i = i + 1
  33:	        }
  34:	        printf("</PRE></BODY></HTML>\n")
  35:	end
  36:	
  37:	def e( str )
  38:	        return( str == nil ? "" : CGI::escapeHTML(str) )
  39:	end
  40:	
  41:	main()
Ruby では、require で、必要なライブラリを読み込む。 def から end までがメソッドの定義である。
main() では、CGI.new() により、CGI クラスのインスタンスを生成している。 その結果を @cgi という変数(インスタンス変数、mainの外でも使える)に保存 している。
このプログラムでは、main() という名前のメソッドを定義しているが、、 main() というメソッドから実行が開始させるわけではない。メソッド定義で はないものは、即座に実行される。このプログラムは最後に main() を呼び出 す文がある。これを忘れると何も実行されない。
$SAFE は、グローバル変数である。Ruby では、ファイルからの入力や環境変 数は汚染されたものとして扱われる。$SAFE を 1 にすると、汚染された文字 列でファイルを開くとエラーになる。標準では、0 。安全を確認したら、 obj.untaint() メソッドで汚染を解除する。明示的に obj.taint() で汚染さ せることもできる。
print_header()では、HTTP のヘッダのうち、Content-Type: 行だけを 出力している。
print_content() では、本文を出力している。
関数(メソッド) e() では、CGI ライブラリ (CGIクラス)の CGI::escapeHTML() を呼び出して、安全なものにして表示する。 たとえば、「<」は、「<」と変換している。こ れで、<SCRIPT> のような危険なスクリプトが送り込まれたとし ても「<SCRIPT>」と表示と表示されるだけで、スクリプト は実行されない。
@cgi に保存されたCGI クラスのインスタンスの request_method() メソッド を呼び出すと、"GET" か "POST" が返される。環境変数は、ハッシュ表 ENV に対して ENV['REQUEST_METHOD'] のようにしてもアクセスできるが、環境変 数を CGI クラスで変更してしまうこともあるようである。
@cgi.keys により、パラメタの一覧が配列の形で得られる。配列の各要素につ いて(qh.each)、パラメタ名を得て表示している。この例では、どんなパラメ タでも表示しているので、このようなループになっているが、通常の CGI プ ログラムでは、@cgi['パラメタ名'] のようにして、パラメタの値を文字列と して取り出すだけでよい。
表示例:
実行例:
request_method: GET script_name: /~yas/coins/syspro-2008/No10_files/cgi-printarg-ruby.cgi query_string: lastname=arg1&firstname=arg2&sex=Male&email=arg3 CONTENT_LENGTH: qv[0]: firstname=arg2 qv[1]: lastname=arg1 qv[2]: sex=Male qv[3]: email=arg3
request_method: POST script_name: /~yas/coins/syspro-2008/No10_files/cgi-printarg-ruby.cgi query_string: CONTENT_LENGTH: 48 qv[0]: firstname=arg2 qv[1]: lastname=arg1 qv[2]: sex=Male qv[3]: email=arg3
% echo "a=b&c=d" | ./cgi-printarg-ruby.cgi 
Content-Type: text/html
<HTML><HEAD></HEAD><BODY><PRE>
request_method: 
script_name: 
query_string: 
CONTENT_LENGTH: 
qv[0]: a=b 
qv[1]: c=d 
</PRE></BODY></HTML>
% 
% id 
uid=1013(yas) gid=40(lab) groups=40(lab),510(softadm),500(jikken3)
% 
ここで、uid は、getuid()、 gid は、getgid()、groups は、getgroups() シ
ステムコールの結果である。getgroups() では、複数の GID が返される。
% ls-c ~/syspro/file/ 
fd-print.c
file-copy.c
mmap-head.c
stdio-thru.c
utmp-print.c
wtmp-last10.c
ystat.c
ystat.h
ヒント:ls の結果から、egrep で .c や .h で終わるものを抜き出す。ある
いは、シェルのパタン・マッチの機能を使って、*.c や *.h だけを取り出す。
余裕があれば、-l などのオプションが付けられるようにしなさい。
% ls-dot ~ 
.
..
.cshrc
.emacs
.login
% 
ヒント:ls に -a オプションを付けると、全てのファイルを表示する。
先頭が「.」のものを抜き出す。
csh, tcsh, sh で 「.*」と指定して探す方法もある。
余裕があれば、-l などのオプションが付けられるようにしなさい。
% ls-dir ~ 
Mail
lib
syspro
tmp
% 
ヒント:ls -l で、d から始まる行だけを抜き出す。あるいは、csh の -d や 
test の -d でディレクトリだけを選びだす。
できるだけ上のようにディレクトリの名前だけ表示するようにしなさい。 モードや日付は表示しないようにしなさい。
余裕があれば、-l などのオプションが付けられるようにしなさい。
% ls-size ~yas/syspro/www 
-rwxr-xr-x   1 yas  prof  27056 Jun 12 11:54 cgi-printarg.cgi
-rwxr-xr-x   1 yas  prof  18376 Jun 19 01:29 cgi-hello.cgi
-rw-r--r--   1 yas  prof   5287 Jun 12 11:54 cgi-printarg.c
-rw-r--r--   1 yas  prof   1579 Sep 26  2002 get.c
-rwxr-xr-x   1 yas  prof    985 May 20 17:17 cgi-printarg-ruby.cgi
-rw-r--r--   1 yas  prof    486 Jun 11 21:20 cgi-hello.c
-rw-r--r--   1 yas  prof    297 Sep 22  2003 Makefile
drwxr-xr-x   5 yas  prof    170 Jun 19 01:27 CVS
-rw-r--r--   1 yas  prof     54 Feb 14  2004 data
total 128
% 
ヒント:ls -l の出力を sort コマンドでソートする。
ソートは、第5フィールドで行う。
% wc-lines *.c 
      85     228    1836 proc-uid-print.c
      75     187    1156 pipe-rw-dup.c
      50     152    1141 vaddr-print.c
      46     140    1071 proc-create.c
      67     161    1014 pipe-rw-nodup.c
      38      93     819 signal-int.c
      50      98     802 setjmp-longjmp.c
      32      90     561 run-n.c
      27      66     535 home-print.c
      20      50     424 cont-1.c
      20      50     424 cont-0.c
      25      60     419 t-system.c
      20      40     384 exec-date.c
      14      49     370 arg-print.c
      15      44     355 env-print.c
      12      32     305 cont-2.c
      13      16     174 fork-hello.c
       4      10      60 main-return.c
% 
この課題では、合計(Total)は表示されなくてもよい。
ヒント:wc の出力を sort コマンドでソートする。foreach か for で1つず つ wc コマンドを実行して、全体の結果を sort するか、引数 $* で wc した 後、sort して、合計(Total)の行を削る。
% ps aux  
USER       PID %CPU %MEM      VSZ    RSS  TT  STAT STARTED      TIME COMMAND
root         1   0.0 -0.0    28340    540  ??  S<s   6Jun06   0:22.58 /sbin/laun
root        52   0.0 -0.0    27256    156  ??  Ss    6Jun06   0:00.00 /sbin/dyna
root        58   0.0 -0.2    30632   3536  ??  Ss    6Jun06   0:08.21 kextd
root        91   0.0 -0.0    28240    624  ??  Ss    6Jun06   0:00.03 /usr/sbin/
...
root        52   0.0 -0.0    27256    156  ??  Ss    6Jun06   0:00.00 /sbin/dyna
root        58   0.0 -0.2    30632   3536  ??  Ss    6Jun06   0:08.21 kextd
root        91   0.0 -0.0    28240    624  ??  Ss    6Jun06   0:00.03 /usr/sbin/
root        92   0.0 -0.2    28360   3624  ??  Ss    6Jun06   9:41.29 /usr/sbin/
% 
そのうち、メモリのサイズ(RSS)が大きいプロセスを 10 個だけ表示するシェル・
スクリプトを作りなさい。
% ps-rss-top10 
USER       PID %CPU %MEM      VSZ    RSS  TT  STAT STARTED      TIME COMMAND
root       239   0.0 -0.9    62428  18316  ??  Ss    6Jun06   2:16.88 /System/Li
security 21102   0.1 -0.8   247056  15876  ??  S    Sat04PM   5:17.79 /System/Li
windowse 21096   0.1 -0.5   203808  11108  ??  Ss   Sat04PM   4:32.14 /System/Li
daemon     242   0.0 -0.4    73932   9280  ??  Ss    6Jun06   1:41.18 /System/Li
root       115   0.0 -0.4    39260   7920  ??  Ss    6Jun06   1:50.21 /System/Li
root       104   0.0 -0.2    34128   5100  ??  Ss    6Jun06   2:47.36 /usr/sbin/
root        92   0.0 -0.2    28360   3624  ??  Ss    6Jun06   9:41.81 /usr/sbin/
root        58   0.0 -0.2    30632   3536  ??  Ss    6Jun06   0:08.21 kextd
root       132   0.0 -0.2    31460   3372  ??  Ss    6Jun06   1:21.59 /usr/sbin/
root     21094   0.0 -0.1   205696   2924  ??  Ss   Sat04PM   0:00.19 /System/Li
ヒント:1行目は、そのまま表示する。
RSS の順(第6フィールド)に sort して head する。
余裕があれば、VSZ の順、CPU 時間の順に表示するスクリプトを作りなさい。
類似のことを実行するプログラムとして top がある。
% diff-backup *.c 
ヒント:foreach または for 文で、引数のファイルについて、
"$file"~ のような名前のファイルが存在するかを調べる。
存在すれば、diff コマンドで表示する。
余裕があれば、比較しているファイルの名前を表示したり、ファイルごとに停 止する、引数を取る、diff に対するオプションを取る、などの工夫をしなさ い。
% mv-lower [A-Z]* 
ヒント:ファイル名を echo して、tr で小文字にして、それを `` でシェル変数に入れる。元の名前から小文 字の名前に mv で変える。
余裕があれば、大文字と小文字を変換することで、ファイルが上書きされる時 には警告を出したり、ユーザに問い合わせたりするようにしなさい。
% countdown 5 
5
4
3
2
1
0
% 
ヒント:sleep 1 で、1ごとに止める。
% show-n-m 10 20 filename 
余裕があれば、-n オプションを付けなさい。これは、ファイルに行番号を振 るものである。nl コマンドを使うとよい。(cat -n が使えるシステムもある。)
ヒント:strtol() のように、文字列から整数値を得るには、Ruby では、 Integer() を使って次のように行う方法がある。
     s = "100"
     i = Integer(s)
ヒント: CGI オブジェクトの params() メソッドを使い、配列の形で得る。
ヒント: CGI オブジェクトでは、ファイルの場合、StringIO クラスのオブジェ クトになる。IOクラスのオブジェクトのように、read(), each(), gets(), readline() といったメソッドが使える。