argius note

プログラミング関連

メモ: ループ制御

(Cygwin, Fedora Core, HP-UX の比較的新しいもので評価しています)
苦労の甲斐あってか、最近はshだけでも色々書けるようになってきました。特に制御関連は良く分かっていなかったので、これを中心に復習していきます。今回は、whileとforを例を挙げて練習してみます。
※サンプル中のshebangは省略しています(shで実行すればshebangも実行権も要らないので)。

標準入力を1行ずつ処理する

whileは他の言語同様、評価が真の場合はブロック内の処理を繰り返します。評価が真というのは、shの場合はステータス($?)が0の場合を指します。
下記のサンプルは、標準入力から得たものを行番号をつけて出力します。

NUMBER=0
while read LINE
do
	NUMBER=`expr ${NUMBER} + 1`
	echo "line:${NUMBER}:${LINE}"
done
$ sh while.sh < while.sh
line:1:NUMBER=0
line:2:while read LINE
line:3:do
line:4:NUMBER=`expr ${NUMBER} + 1`
line:5:echo "line:${NUMBER}:${LINE}"
line:6:done
$ sh while.sh
a\ta
^Z
$

whileは、readが0を返す限りブロック内(do〜done)を繰り返し処理します。
TABが何故か出力されないのですが、ファイルではなく入力待ち状態から実行(2回目)してみるとちゃんとタブは出力されました。今のところ、何故こうなるかは分かりません。(a\taのところは実際はTABキーで入力、^Zは"Ctrl+Z"のことでEOFになります。)

コマンドを数回試行する

shスクリプトで、コマンドのリトライ制御を今まで見たことがなかったので、自分で考えてみました。これが良いやり方なのかどうかちょっと自身はありません。まずはforでやってみます。

forは、スクリプト言語で言うforeachにあたる処理で、与えられたリストを1要素ずつ処理します。

for i in 1 2 4
do
	echo $i
done
$ sh for.sh
1
2
4
$

inの後ろにスペース区切りの要素リストがあれば、それを繰り返し処理するので、*を指定すればカレントディレクトリのファイルリスト(オプションなしlsと同じ)が得られ、ファイルごとの処理が出来ます。
これを利用して、コマンドの試行処理を書いてみます。

ST=1
for i in 1 2 3
do
	${COMMAND} # command to check
	if [ $? -eq 0 ]; then
		ST=0
		break
	fi
done
if [ $ST -eq 0 ]; then
	echo "command normally end"
else
	echo "command abnormally end"
fi
$ export COMMAND=date
$ sh retry.sh
Wed Mar 21 20:54:27 JST 2007
command normally end
$ export COMMAND=a
$ sh 20070321.retry.sh
retry.sh: line 4: a: command not found
retry.sh: line 4: a: command not found
retry.sh: line 4: a: command not found
command abnormally end
$

上手くいきました。ここで、ちょっと気になる点があります。もし試行回数を10回とか20回に変えたいとしたらどうしましょう。PerlRubyでは、..演算子で連続した値のリストを作ることができます。shの場合はどうするのか分からないので、これも考えてみました。

ひとつは、もう"外"の力に頼ってしまう方法。retryの部分は省略しています。

CONT=32
for i in `perl -e "print '0 ' x ${CONT}"` # 0 is dummy
do
	echo -n "+"
done
$ sh loop1.sh
++++++++++++++++++++++++++++++++$

Perlにリストを作ってもらいます。上記の処理では、スペースで区切られた32個のゼロが返されるので、CONTで指定した32回の繰り返しが実現できます。割とすっきり書けますが、Perlが分からないと意味不明ですし、このくらいはshだけでなんとかしたいところです。

そこで、次はループカウンタを使ってやってみました。つまり、一般的な言語のforeachでないほうのforと同じことをすれば良いわけです。最初からここに行き着けば良かったのですが、考えすぎてたようです。

CONT=32
i=$CONT
while [ $i -gt 0 ]
do
	echo -n "+"
	i=`expr ${i} - 1`
done
$ sh loop2.sh
++++++++++++++++++++++++++++++++$

これは上手くいくのですが、とても遅くなりました。最初のもそうですが、whileは遅いのでしょうか。
最後は、ひとつ前と同じですが、trueを使ってループの中で条件判定を行っています。trueは必ず0を返す"コマンド"です。

CONT=32
i=$CONT
while true
do
	i=`expr ${i} - 1`
	if [ $i -lt 0 ]; then
		break
	fi
	echo -n "+"
done