Logo address

rc - the Plan9 shell (2) -

2002/03/11
2002/09/02 改訂
2016/05/12 追加
ここでは rc を使いこなす上で役に立つ知識を述べる。
基本的な事は rc(1) に述べているので、まだ rc(1) を読んでいない読者は先に rc(1) を読むのが良い。

配列変数

ここでは複数の文字列を列挙する場合に発生する問題、例えば
a='alice bob'

b=(alice bob)
の使い方の比較をする。
次のファイルを持つディレクトリ
alice
bob
において、先の a と b を使って次の実験をしよう。
term% ls $a
ls: alice bob: file does not exist
term% ls $b
alice
bob
term%
ここで
ls $a

ls 'alice bob'
と同じ効果を持っている。他方
ls $b

ls (alice bob)
と同じであるが、これは
ls alice bob
として処理される。
つまり rc では引用符で囲まれた文字列リストは、全体で一個の文字列と見做される。
rc においては変数の展開規則が UNIX のシェルとは幾分異なるのである。

もっと強力なパターンマッチングが必要な場合には

rc のパターンマッチングはシンプルで多くの場合には充分な機能を持っている。
ところが時にはもっと強力なパターンマッチングが必要な場合がある。
(bash のパターンマッチングは rc よりも強力である。
その代償としてパターンマッチングの使い方は複雑であり、筆者はルールのこじつけのような違和感を覚える。)

Plan9 のメールアドレスの表現は UUCP 形式である。そこで

s='ar.aichi-u.ac.jp!alice'
のようなデータ、即ち ! で区切られた文字列を読み取り ! の左が
*.aichi-u.ac.jp
にマッチすれば ! の右を出力するスクリプトを考えよう。
rc では例えば次のスクリプトのようになるであろう。
switch($s){
case *.aichi-u.ac.jp!*
	ifs=! t=`{echo -n $s} echo Welcome $t(2)
case *
	echo Denied
}
あるいは
ifs='!' t=`{echo -n $s} {
switch($t(1)){
case *.aichi-u.ac.jp
	echo Welcome $t(2)
case *
	echo Denied
}}
でも構わないであろう。
後の書き方において、もしも
ifs='!' a=`{echo -n $s} {
...
}
ではなく
ifs='!' a=`{echo -n $s}
...

と書いたならば ifs の値が変化するから注意しよう。
何故なら、変数への代入が局所的であるには、その後にコマンドが必要だからである。

a=`{echo -n $s}
は代入であり、コマンドではない。

rc はマッチした場合には * の部分のデータを内部では見つけているはずである。
その結果を参照する機能を提供していれば、もっとエレガントな書き方ができたであろう。(bash は rc よりこの点では強力である)
しかしながら rc の開発者はそのようなニーズを認めなかったに違いない。
筆者もこれで困っていない。(強力な処理が必なら sed を使用する)

シェルスクリプトのコマンドオプション

2016/05/12

Cのプログラムだとオプションやフラグの扱いは柔軟である。例えば

usage: foo [-x] [-p pattern] arg ...
の場合には
foo -x -p abc a1 a2
のように分離して書かなくても
foo -xpabc a1 a2
などと詰めて書くことができる。Rc でも同様なことはできないか?
そこで次のようなプロトタイプを作ってみた。
#!/bin/rc
usage='usage: foo [-x] [-p pattern] file ...'
while(~ $1 -*){
	switch($1){
	case -x
		echo $1
		shift
	case -[x]*	# set of flags
		*=(`{echo $1 | sed 's/^(..)(.*)/\1 -\2/'} $*(2-))
	case -p
		echo $1
		shift
		echo $1
		shift
	case -[p]*	# set of options followed by something
		*=(`{echo $1 | sed 's/^(..)(.*)/\1 \2/'} $*(2-))
	case -*
		echo $usage
		exit usage
	}
}
while(~ $1 ?*){
	echo $1
	shift
}
これは期待通りの動作をする。

改良版

もっと一般化して
#!/bin/rc
rfork e
usage='usage: opts [-x] [-p pattern] file ...'
flags='x'
opts='p'
flag=()
opt=()
while(~ $1 -*){
	switch($1){
	case -[$flags]
		flag=($flag $1)
		shift
	case -[$flags]*	# set of flags
		*=(`{echo $1 | sed 's/^(..)(.*)/\1 -\2/'} $*(2-))
	case -[$opts]
		o=`{echo $1 | sed 's/^-(.*)/\1opt/'}
		#echo $o
		opt=($opt $o)
		shift
		$o=$1
		shift
	case -[$opts]*	# set of options followed by a string
		*=(`{echo $1 | sed 's/^(..)(.*)/\1 \2/'} $*(2-))
	case -*
		echo $usage
		exit usage
	}
}
for(f in $flag)
	echo $f
for(o in $opt)
	echo $o $$o

while(~ $1 ?*){
	echo $1
	shift
}

環境変数はシェル変数とは限らない

rc のシェル変数は自動的に環境変数になるが、逆は真ではない。
この問題があらわになるのは HTML の form パーサを使用した時である。
Plan9 の form パーサはいくつかあるが、筆者は自作のものを使用している。
(qsparse と言う名称である。)
qsparse$QUERY_STRING を読み取り、その結果を環境変数に落とす。
例えば $QUERY_STRING
name=alice&age=18
とせよ。そのもとで
qsparse $QUERY_STRING
を実行すると、環境変数 QS_nameQS_age に各々 'alice' と '18' が納められるように設計されている。
所がこのままでは $QS_name$QS_age も値を持たない。まだシェル変数になっていないからである。これらをシェル変数にするには
QS_name=`{cat /env/QS_name}
QS_age=`{cat /env/QS_age}
を実行する必要があるのである。これはいささか面倒であり、また間違い易いのであるが、よい方法はなさそうである。
精々次のように纏めてシェル変数に変換する事ぐらいか?
cd /env; for(a in QS_*) $a=`{cat $a}

ところで UNIX の場合にはどうであろうか?
UNIX においては環境変数は子プロセスに引き継ぐ事はできるが、親プロセスに渡す事はできない。すなわち qsparse のような QUERY_STRING を解析するツールを作っても何の役にも立たないのである。

ループ

途中での抜け出し

2016/05/12

Bash ではループに対する break を持っているが Rc は持たない。ではどうするか?

a=`{for( i in doc src tmp) test -r $i && echo $i && exit}
echo $a
これを実行するとカレントディレクトリに doc, src, tmp の何れかが見つかった時に for ループから抜け出す。多くの場合には抜け出した理由を知りたいのであるから、これで良し、break は要らないと考えたのであろう。なお、exit によって Rc スクリプトが終了しないのは
`{....}
がサブシェルで実行されているからである。