ルー

ループ の Bash

{other: "%{count}個の演習"}

ループについて

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を使うべきでないとき

forで行を読み込んではいけません。

よく見かけるアンチパターンがあります。catの出力を繰り返し処理する、というものです。

for line in $(cat some.file); do 
    do_something_with "$line"
done

これには2つの問題があります。

  1. コマンド置換が引用符で囲まれていないため、単語分割の対象になります。 単語分割は、既定では改行だけでなく_空白_でも分割します。 このforループは、ファイルの行ではなく、ファイル内の単語を繰り返し処理しています。
  2. コマンド置換が引用符で囲まれていないため、ファイル名展開の対象になります。 ファイル内のすべての単語が、グロブパターンとして照合されます。

引用のコンセプトで学んだように、単語分割は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に空文字列を代入します。 これにより単語分割が一時的にオフになり、読み込んだ行の先頭や末尾の空白がそのまま保たれます。

until

until構文は、ある条件が偽の間、一連のコマンドを繰り返します。 ループは、条件が真になる_まで_繰り返されます。

until CONDITION_COMMANDS; do COMMANDS; done

untilを使うことはほとんどありません。 むしろ、条件を否定したwhileループを使うほうが一般的です。

while ! CONDITION_COMANDS; do ...

無限ループ

ずっと繰り返したいこともあります。 その方法は2つあります。

  1. 常に成功の終了ステータスになる条件を指定したwhileループ

    while true; do ...
    
  2. 式を空にした算術forループ

    for ((;;)); do ...
    

do-while

do-while構文は明示的には用意されていませんが、同じ効果を得ることはできます。

while true; do
    COMMANDS

    if END_CONDITION; then
        break
    fi
done
GitHubで編集 リンクは新しいウィンドウまたはタブで開きます