while文は繰り返し処理をしたい場合に使用します。
- while文の基本形
-
while 条件式
do
コマンド (条件式が真である間繰り返し実行される)
done
while文の例を記します。
#!/bin/bash cnt=0 while [ $cnt -lt 10 ] do cnt=`expr ${cnt} + 1` echo $cnt done
上記シェルスクリプトは「cnt」変数が10より小さい間は条件式が真となりdo~doneまでの処理が実行されます。
まずは実行してみましょう。
ファイル名「prog007.sh」として保存したときの実行例です。
$ chmod u+x prog007.sh $ ./prog007.sh 1 2 3 4 5 6 7 8 9 10
do~doneまでの処理が10回繰り返されていることがわかります。
11回目に条件式に処理が渡ったときは判定で偽が返されwhile文を終了します。
条件式で使われている「-lt」演算子は「左辺が右辺より小さい」という意味を表します。数値の比較は「<」のような記号ではできないので注意してください。
数値比較演算子
演算子 | 意味 |
---|---|
-eq | 等しい(equal to) |
-ne | 等しくない(not equal to) |
-lt | より小さい(less than) |
-le | 以下(less than or equal to) |
-gt | より大きい(greater than) |
-ge | 以上(greater than or equal to) |
リダイレクトを使ってCSVファイルを読み込む
リダイレクトを使ってファイルからの入力をwhile文に渡すことができます。
以下は、CSVファイルから値を読み込み、出力するサンプルです。
#!/bin/bash CSVFILE="/tmp/sample.csv" while IFS=, read aa bb cc; do aa_arr+=($aa) bb_arr+=($bb) cc_arr+=($cc) done < $CSVFILE arr_max=`expr ${#aa_arr[*]}` i=0 while [ $i -lt $arr_max ] do echo ${aa_arr[$i]},${bb_arr[$i]},${cc_arr[$i]} i=`expr $i + 1` done
実行結果
A1,B1,C2 A2,B2,C2 A3,B3,C3
パイプを使ってCSVファイルを読み込む
CSVファイルは「/tmp/sample.csv」という名前で以下内容が保存されているとします。
A1,B1,C2 A2,B2,C2 A3,B3,C3
このCSVファイルをパイプを使って読み込みます。
#!/bin/bash CSVFILE="/tmp/sample.csv" cat $CSVFILE | while read aa bb cc; do aa_arr+=($aa) bb_arr+=($bb) cc_arr+=($cc) done echo ${aa_arr[0]} arr_max=${#aa_arr[*]} i=0 while [ $i -lt $arr_max ] do echo ${aa_arr[$i]},${bb_arr[$i]},${cc_arr[$i]} i=`expr $i + 1` done
このシェルスクリプトを実行した場合、リダイレクトを使ったひとつ前のサンプルと同じ実行結果になるように思うのではないでしょうか。ただ、実際、実行してみると、結果は何も出力されません。
原因を探るため、デバッグモードで実行してみます。
# sh -x ./sample.sh + CSVFILE=/tmp/sample.csv + cat /tmp/sample.csv + read aa bb cc + aa_arr+=($aa) + bb_arr+=($bb) + cc_arr+=($cc) + read aa bb cc + aa_arr+=($aa) + bb_arr+=($bb) + cc_arr+=($cc) + read aa bb cc + aa_arr+=($aa) + bb_arr+=($bb) + cc_arr+=($cc) + read aa bb cc + echo + arr_max=0 + i=0 + '[' 0 -lt 0 ']'
シェル変数配列aa_arr、bb_arr、cc_arrには値を格納できているように見えますが、配列の要素数は0となっており、後半のwhile文では、一度もループせずに終了しています。配列に値を格納できているのに、実際は何も格納されていないという不可解が現象が起きています。
この原因を知るためには、シェルとコマンドの関係を理解する必要があります。
シェルとコマンドの関係
シェル変数はシェルごとに定義されるということを知っておく必要があります。それを証明してみます。
# pwd /root # bash # PS1="$USER: " root: cd /tmp root: pwd /tmp root: exit # pwd /root #
シェルを起動してからシェル変数PS1を変更しています。シェル変数PS1はプロンプト文字を格納するあらかじめ定義されている特殊な変数です。PS1を変更することで直ちにプロンプト文字が変更されます。また、シェル変数USERも特殊な変数で、現在のユーザー名が格納されています。
PS1にUSERの値を格納したことで、プロンプトが現在のユーザー名に変わります。
次にcdコマンドでカレントディレクトリを変更し、pwdコマンドで、カレントディレクトリを確認します。
ここで、exitでシェルを終了します。
その瞬間、プロンプト文字は元に戻り、カレントディレクトリも戻っています。
以上のことから、別のシェルでシェル変数を変更したりcdコマンドを実行したりしても、他のシェルには影響していないことがわかります。
シェルスクリプトを実行することでシェルが起動します。起動するシェルは1つのプロセスです。このシェルからコマンドを実行した場合、シェルのプロセスはもうひとつのプロセスを作ります。元のシェルのプロセスは親プロセス、作成されたプロセスは子プロセスと呼びます。
この後、子プロセスはコマンドを実行します。コマンドが終了したらプロセスも終了し、元のシェルに戻ります。
では、前置きが長くなりましたが、パイプを使ってCSVファイルを読み込んだ際、シェル変数配列に格納したはずの値が出力されなかった理由を改めて考えてみます。
サンプルのシェルスクリプトではcatコマンドで出力した内容をパイプを使ってwhile文に渡し、そのループの中で、シェル変数配列に値を格納しています。
cat $CSVFILE | while read aa bb cc; do aa_arr+=($aa) bb_arr+=($bb) cc_arr+=($cc) done
このcatコマンドが終了したら、そのプロセスは終了し、その中で定義したシェル変数も破棄されます。つまり、上記例では、シェル変数配列aa_arr、bb_arr、cc_arrはwhile文の中でしか存在できず、while文を抜けてcatコマンドが終了した時点ではもう存在していないということになります。これがパイプを使ってCSVファイルを読み込んだ際、シェル変数配列に格納したはずの値が出力されたかった理由です。
なお、cdコマンドなどはビルトインコマンドと呼ばれ、同じシェルの中で実行されます。cdコマンドが別のシェルで実行されては意味がありません。