Bashのループには2つの形式があります。
forループ項目のリストを繰り返し処理するには、forループを使います。
for varname in words; do COMMANDS; done
wordsは、単語分割とファイル名展開(引用のコンセプトで学びました)によって展開されます。
各単語は順番にvarnameへ代入され、COMMANDSが実行されます。
forループが役立つのはどんなときか、例を見てみましょう。
複数のファイルに対してコマンドを実行する。
# source all the bash library files in the current directory
for file in ./*.bash; do
source "$file"
done
スクリプトや関数の各引数に対してコマンドを実行する。
for arg in "$@"; do
echo "argument: ${arg}"
done
コマンドの出力を空白で区切った単語ごとにコマンドを実行する。
for i in $(seq 10); do
echo "${i} squared is $((i * i))"
done
この書き方の注意点は、後ほど見ていきます。
forループBashには算術ループもあります。 他のプログラミング言語を触ったことがあれば、どこか見覚えがあるでしょう。
for ((INITIALIZATION; CONDITION; INCREMENT)); do COMMANDS; done
bashの二重括弧((( )))は算術コンテキストを表します(bashの算術については、後のコンセプトで詳しく見ていきます)。
先ほどのseqを使った例は、次のように書けます。
for ((i = 1; i <= 10; i++)); do
echo "${i} squared is $((i * i))"
done
forを使うべきでないときよく見かけるアンチパターンがあります。catの出力を繰り返し処理する、というものです。
for line in $(cat some.file); do
do_something_with "$line"
done
これには2つの問題があります。
forループは、ファイルの行ではなく、ファイル内の単語を繰り返し処理しています。引用のコンセプトで学んだように、単語分割はIFS変数で制御でき、ファイル名展開はオフにできます。
しかし、これは壊れやすい解決策になりがちです。
ファイルの行を繰り返し処理するのに、forは適した方法ではありません。
ファイルを読むための定石は、whileループを使うことです。
whileループある条件が真の_間_、一連のコマンドを繰り返すにはwhileを使います。
while CONDITION_COMMANDS; do COMMANDS; done
ifのときと同じように(条件分岐のコンセプトで学びました)、CONDITION_COMMANDSに特別な構文はありません。
コマンドリストの終了ステータスが「真」か「偽」かを決めます。
breakコマンドはループから抜け出します。
制御は、ループの終わりを示すdoneの次のコマンドに移ります。
continueコマンドは、ループの次の繰り返しに進みます。
すでに触れましたが、ファイルを読むための定石はwhileループです。
while IFS= read -r line; do
do_something_with "$line"
done < some.file
ファイルの内容は、<リダイレクトによってループへの入力として渡されます。
readコマンドは、入力から1行を完全に読み込めると「真」を返します。
入力を使い切ったとき、または最後の行が改行で終わっていない場合、readは「偽」を返します。
最後の行が改行で終わっていなくてもファイルの行を読み込む、_本当に_定石と言える方法は次のとおりです。
while IFS= read -r line || [[ -n "$line" ]]; do ...
-rオプションは、readにバックスラッシュのシーケンスを置換しないよう指示します。バックスラッシュは、ただの普通の文字として扱われます。
IFS=は、readコマンドが実行されている間だけIFSに空文字列を代入します。
これにより単語分割が一時的にオフになり、読み込んだ行の先頭や末尾の空白がそのまま保たれます。
untiluntil構文は、ある条件が偽の間、一連のコマンドを繰り返します。
ループは、条件が真になる_まで_繰り返されます。
until CONDITION_COMMANDS; do COMMANDS; done
untilを使うことはほとんどありません。
むしろ、条件を否定したwhileループを使うほうが一般的です。
while ! CONDITION_COMANDS; do ...
ずっと繰り返したいこともあります。 その方法は2つあります。
常に成功の終了ステータスになる条件を指定したwhileループ
while true; do ...
式を空にした算術forループ
for ((;;)); do ...
do-while構文は明示的には用意されていませんが、同じ効果を得ることはできます。
while true; do
COMMANDS
if END_CONDITION; then
break
fi
done