メモ: ループ制御
(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回に変えたいとしたらどうしましょう。PerlやRubyでは、..演算子で連続した値のリストを作ることができます。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