if( ){ } while( ){ }
#!/bin/rc # coded by Kenar # First we should check the validity of the operation for(f in $*){ if(test -e $f.orig){ echo $f.orig already exist! exit } } for(f in $*) cp -x $f $f.orig
譜 dup
ここに cp
の -x
オプションは日付とアクセスモードを保存するオプションである。 dup
はまず foo.orig
が存在しない事を確認する。もし存在すれば、その事を指摘し、中断する。さらに dup
は複数のファイルを処理する。
if [ ... ]; then
の [ ]
は Bourne shell の構文の一部ではない。記号 [
はコマンド名であり、この変なコマンドは test
へのリンクである。[
コマンドは最後の引数に ]
を要求し、]
に続く ;
は [
コマンドのセパレータである。[ ]
の中は一つ一つがコマンドの引数で、if [ "$a" = "$b" ]; then
if(~ $a $b){}
~
はコマンドではない。)
( )
の中でマッチング演算子 ~
を使用できる。例を挙げよう。#!/bin/rc -e fn usage { echo 'pack source source ...' >[1=2] exit usage } # # foo -> foo.gz in case of file. # foo -> foo.tar.gz in case of dir. # foo.tar -> foo.tar.gz in case of file. # foo.tar.gz -> DO NOTHING in case of file. # # code by Kenar # while (~ $1 -*){ switch($1){ case -* usage } shift } while (! ~ $#* 0){ x=$1 if (test -d $x ){ tar -cf $x.tar $x x=$x.tar } if (test -e $x){ if(! ~ $x *.gz) gzip $x } shift }この例の
~ $1 -*
$1
が -*
にマッチした場合に真となる。但し rc のマッチングは正規表現のマッチングではなくシェルスタイルのマッチングである。また $#*
は $*
(コマンド引数の残り)の要素数を表し、! ~ $#1 0
!
' は否定演算子である。(つまりこの場合 $1
の長さが 0 ではない場合に真となる。)
switch 文の case ラベルでも Bourne shell と同様にパターンマッチングが使える。
switch 文のケースラベルにせよ、while 文や if 文のマッチング式にせよ、
パターンは複数指定できる。その場合
switch($user){ case alice bob .... case carol .... }
switch 文の書き方は Bourne shell や C-shell に比べて簡単になっている事に注意せよ。例えば 各 case の終わりに、
;; # Bourne shell
breadksw # C-shell
a
へ 'alice
' を代入するには、a=alice
a='alice'
a=(alice)
複数の要素を扱う時には丸括弧を使う。
u=(alice bob carol) u=(alice (bob carol))
u(1), u(2), u(3)
u
はv='alice bob carol'
'alice bob carol'
だけを持つ配列でv=('alice bob carol')
X
を shell 変数とする時、配列の要素の個数は $#X
で表し、配列の値は $X
で表す。
また配列の要素 1 の値は $X(1)
で表す。
また shell 変数の取り消しは
X=()
シェル名 | 代入法 | 要素の個数 | 変数の値 | 変数の要素の値 |
---|---|---|---|---|
rc | u=(alice bob carol) |
$#u |
$u |
$u(1) $u(2) $u(3) |
csh | set u=(alice bob carol) |
$#u |
$u |
$u[1] $u[2] $u[3] |
rcの部分配列の範囲は "-
" を使って表現する。例えば
term% a=(alice bob carol david) term% echo $a alice bob carol david term% echo $a(1-3) alice bob carol term% echo $a(2-3) bob carol term% echo $a(2-) bob carol david term%
*
$*
は引数の1次元配列の値を表している。従って rc では *
は特殊な shell 変数に過ぎない。実際 rc では*=(alice bob carol)
$1
$2
... は $*(1)
$*(2)
... の省略形である。
whatis
echo
では不十分である。実際、w=('alice bob' carol) v='alice bob carol'
echo $w echo $v
alice bob carol
whatis
が威力を発揮する。例えば、term% whatis w w=('alice bob' carol) term% wahtis v v='alice bob carol'
u
を配列 (alice bob carol)
とする。このの要素を纏めて、文字列 'alice bob carol'
として扱いたい場合がある。この場合には演算子 $"
を使用する。v=$"u
x=(alice bob) y=(carol david)
z=(alice bob carol david)
z=($x $y)
x
の要素に 'carol' を付け加えたいだけであればx=($x carol)
^
^
が用意されている。実行例を挙げよう。term% a=alice term% b=bob term% x=$a^$b term% whatis x x=alicebobこれは文字列同士の単純な連結である。
配列と文字列の場合には
term% c=(alice bob carol) term% x=$c^.z term% whatis x x=(alice.z bob.z carol.z)即ち、数学的な言い方をすると、ベクトルとスカラーの積のような振る舞いをする。
2つの配列の場合には、ベクトルの内積のように振る舞う。但し配列要素の個数が異なるとエラーになる。
term% d=('Miss ' Mr. Mrs.) term% x=$d^$c term% whatis x x=('Miss alice' Mr.bob Mrs.carol)結合演算子 "
^
" は、これが無くても区切りが明確な場合には省略できる。$a^$b
は $a$b
と書いてもよく、また $c^.z
は $c.z
と書いてもよい。しかしながら、$c^z
を $cz
と書く事はできない。(cz
が変数名と見做される。)
Jul=7 a=Jul echo $$a
291a
10018a
Jan=01; Feb=02; Mar=03; Apr=04; May=05; Jun=06 Jul=07; Aug=08; Sep=09; Oct=10; Nov=11; Dec=12 c=`{date} yy=`{echo $c(6) | sed s/^20//} mm=$$c(2) mbox=9fans.$yy$mm
date
の出力、即ち、Mon Jul 9 07:55:40 JST 2001
mbox=9fans.0107
term% name=alice term% name=bob echo $name bob term% echo $name alice即ち変数への代入の後にコマンドが来れば、その代入はコマンドに対してローカルであると見なされる。代入は
name=alice age=18 echo $name $age
name=alice age=18 { echo name age echo $name $age }
a=`{command}
`{....}
{ }
内のコマンドの実行結果を意味する。( Bourne shell のスタイルで書くと `....` である。){... }
を使った shell 変数への代入に対しては(そしてこの場合にのみ) ifs
(input field separator) が使用される。b=alice!bob!carol ifs=! a=`{echo -n $b}
a=(alice bob carol)
ifs
の値が変化する。従って副作用をさけるためにはifs_save=ifs ifs=! a=`{echo -n $b} ifs=ifs_save
ifs=! {a=`{echo -n $b}}
echo
の -n
オプションは改行を出力しない事を意味する。このオプションが無いと $a(3)
は 'carol' にはならずに末尾に改行コードが付加されてしまう。
rc では ifs
が利用される場面は Bourne shell に比べて厳しく制限されている。( Bourne shell の IFS
は思わぬ落とし穴をもたらす事があり、セキュリティホールの原因になる。)
Web の QUERY でお目にかかる問題について考えよう。
QUERY='alice=16&bob=20'
QUERY
を基にalice=18 bob=20
ifs='&' q=`{echo -n $QUERY}
$q
の内容は('alice=18' 'bob=20')
for(x in $q){ ifs='=' y=`{echo -n $x} $y(1)=$y(2) }
alice
と bob
には各々 18
と 20
が代入される。このことは実際にecho $alice echo $bob
eval
を使わなくてもよいのである。QUERY='mail someone</etc/passwd;alice=16&bob=20'
'mail someone</etc/passwd;alice'
18
が代入されているのである。echo $'mail someone</etc/passwd;alice'
/env
に置かれている。環境変数 u
へ値 alice
を代入するにはecho -n alice > /env/u
u
の値はcat /env/u
u=alice
u
へ代入が行われる。また shell変数 u
が存在しなければ、echo $u
$u
が shell 変数 u
の値であると言うのは正しくない。)
読者は shell 変数と環境変数が一致しない状況を
u=alice echo -n bob >/env/u echo $u # alice を出力する cat /env/u # bob を出力する
以上のルールは巧く働いている。
子プロセスへは shell 変数は引き継がれない。Plan9 では(UNIX と異なり)親子で環境変数を共有する事ができる。その場合も子プロセスにおける環境変数や shell 変数の変化は親プロセスの shell 変数に影響を与えない。shell の再帰的実行が巧く行くと言う訳だ。
"
には特別の意味はない。ただの文字である。
Bourne shell では "
は '
とは似ているが、"...."
の中ではシェル変数は展開され、また` によってコマンドの実行結果を展開できた。しかし Bourne shell に於てもこの機能は要らないのである。
例えば Bourne shell では
"alice $u carol"
'alice '$u' carol'
"alice `date` carol"
'alice '`date`' carol'
$
や `
の展開は全て '...'
の外で行うのである。
rc では二重引用符をシェルの制御から外された事によって awk などが非常に書き易くなった。例えば、Bourne shell のスクリプトの中でファイル名とデータ alice
を引数 $1
, $2
としてawk に渡したい時には
#!/bin/sh awk "{if(\$4==\"$2\" && \$5!=\"*\") printf\"%s %4s %s %s\\n\", \ \$1,\$5,\$2,\$3}" $1と書く。つまり
\
のお化けができ上がるのである。#!/bin/rc awk '{if($4=='"$2"' && $5!="*") printf "%s %4s %s %s\n", \ $1,$5,$2,$3}' $1
alice
の行をとりだしかつ第5項が *
の行を捨て、そして項を $1
,$5
,$2
,$3
の順に並べ変えよ。arisawa 95/08/24 20:27:21 alice 66% arisawa 95/08/25 16:52:25 unix * arisawa 95/08/25 16:54:53 alice * arisawa 95/08/25 16:55:44 unix 50% arisawa 95/08/25 16:57:12 alice 83% arisawa 95/08/25 16:57:37 alice 100%
> >> < << |
sh: echo .... 1>&2 rc: echo .... >[1=2]
プログラム foo
の標準出力と標準エラーを共にファイル a
へ出力したい場合には、
sh: (2>&1 foo) > a sh: (foo 2>&1) >a rc: {foo >[2=1]} >a rc: {>[2=1] foo} >a
foo
の標準出力と標準エラーを共に他のプログラム bar
の入力として渡したい場合には、sh: 2>&1 foo | bar sh: foo 2>&1 | bar rc: foo >[2=1] | bar rc: {>[2=1] foo} | bar
foo
の標準エラーをファイル a
へ出力したい場合にはsh: foo 2> a rc: foo >[2] a
echo one>a echo 1>a
2つのプログラム foo
と bar
の出力を比較したい場合には rc では
cmp <{foo} <{bar}
<{program}
さて rc
では >[2] の自然な拡張として |[2] によってエラーメッセージだけをパイプに渡す事ができる。つぎはその実行例である。
term% ls aaa |[2] tee /tmp/x ls: aaa: 'aaa' file does not exist term% cat /tmp/x ls: aaa: 'aaa' file does not exist term% ls -l x |[2] tee /tmp/y --rw-rw-r-- M 9 arisawa arisawa 35 May 3 00:23 x term% cat /tmp/y term%
sh: foo(){ .... } bash: foo(){ .... } bash: function foo(){ .... } rc: fn foo { .... }
function
を省く形も許している。関数引数は何れも$1
, $2
,... あるいは全て纏めて $*
で参照できる。
局所変数 x
, y
が必要な時には
bash: foo(){ local x y; .... } bash: function foo(){ local x y; .... } rc: fn foo { x=x0 y=y0 { .... }}
x0
と y0
は初期値であり、必ず与えなければならない。
筆者は局所変数を bash 流にキーワード local
で指定する方が好きである。では rc は何故 local
を導入しなかったのであろうか? rc の設計者は不要なものは作らない主義なのである。実際、 rc における関数の局所変数は以下のように自然にでてくる。
次のコマンドを sh, bash, rc で実行してみよう。
a=aaa b=bbb; a=alice b=bob echo $a $b; echo $a $b
sh: aaa bbb alice bob
bash: aaa bbb aaa bbb
rc: alice bob aaa bbb
{ }
{
と }
は (
と )
と同様にコマンドのグループ化記号である。( ... )
はサブシェルで実行するが { ... }
はそうではない。rc の { ... }
もその点では Bourne shell と同じである。なお rc に於てサブシェルでの実行を指示するには { ... }
の前に @
を付ける。
Bourne shell における { ... }
は冷や飯を食わされている。活用されていないばかりか(文献[3])、
(foo;bar)
{foo;bar}
{foo;bar;}
name rc sh size(bin) 102k 299k size(src) 90k 380k
rc は sh と同様、履歴機能を持たない。Plan 9 ではこのことは全く問題にならない。Plan 9 では過去に現れた入出力データを再利用する問題はシェルではなくてウィンドウが担っているからだ。しかし Unix ではつらい。スクリプト用として使えば良いであろう。
rc は sh と同様に外部コマンドでやって行けるものは内部で処理されない。(rc の read
は外部コマンドである。sh と異なり外部コマンドでやって行ける仕様だから。)
read
や echo
の様に頻繁に使うコマンドを外部処理すれば速度が犠牲になる。これは哲学の問題である。筆者は美しさよりも処理速度の方が気になり、「read
や echo
は内部の方が...コードの増加はたかが知れている...」と思う方である。もっとも rc はこのままでも軽快に使える。
次の引用は bash のマニュアルからである。