現場で使えるシェルスクリプト入門10:for文の高度な使い方とループ処理のベストプラクティス

シェルスクリプト IT

以前の記事(入門5)では、配列と組み合わせた基本的な for ループの使い方について解説しました。

今回は、さらに一歩進んだfor文の高度な使い方を解説します。ただ、高度なテクニックに入る前に、まずはシェルスクリプトの for 文がどのようなルールで動くのか、その「基礎的な使い方」を簡単におさらいしておきましょう。

for文の基本構文とシンプルな繰り返し処理

シェルスクリプトにおける for 文の最もシンプルな構文は以下の通りです。

for 変数 in 値1 値2 値3 ...; do
    # 繰り返し実行したい処理(変数を参照できる)
done

in の後ろにスペース区切りで並べた値が、順番に 変数 に代入され、do から done までの処理が値の数だけ繰り返し実行されます。

💡 基本的な実例

# フルーツの名前を順番に出力するシンプルな例
for fruit in Apple Banana Cherry; do
    echo "フルーツ名: $fruit"
done

実行結果:

フルーツ名: Apple
フルーツ名: Banana
フルーツ名: Cherry

このように、あらかじめ決まった文字列や値を順番に処理するのが for 文の基本です。この挙動を頭に入れた上で、現場でよく使う「ファイル一括処理」などの応用テクニックを見ていきましょう。

ワイルドカード(*)を使ったファイル一括処理と「空振り」対策

現場で多いのが、特定のディレクトリ内にある複数のファイルを順番に処理するタスクです。シェルスクリプトでは、* などのワイルドカードを使って直感的に処理できます。

# /var/log/app/ ディレクトリ内のすべての .log ファイルをループ処理
for filepath in /var/log/app/*.log; do
    echo "処理中のファイル: $filepath"
    # ここに圧縮やバックアップなどの処理を書く
done

⚠️ 落とし穴:対象ファイルが無いときの「空振り」

もし /var/log/app/ の中に .log ファイルが1つも存在しなかった場合、このループはどうなるでしょうか?
「0回実行されて終わる」と思いきや、実は文字通りの/var/log/app/*.log」という存在しないファイル名としてループが1回だけ実行されてしまいます(これがエラーの原因になります)。

これを防ぐためには、スクリプトの冒頭でシェルオプション nullglob を有効にするのがクリーンな解決策です。

# nullglobを有効にすると、一致するファイルがない場合に文字通り「空」として扱い、ループを0回で終了します
shopt -s nullglob

for filepath in /var/log/app/*.log; do
    # ファイルがある場合のみ実行される
    echo "処理中: $filepath"
done

# (必要に応じて)元の挙動に戻す場合
shopt -u nullglob

コマンド出力結果のループ

$(コマンド)(コマンド置換)を使って、別のコマンドが吐き出したテキストデータを行ごとにループ処理したい場合があります。

# 例:findコマンドで探したファイルをループで処理する
for file in $(find . -name "*.txt"); do
    echo "見つかったファイル: $file"
done

⚠️ 落とし穴:スペースが含まれるとバラバラに分解される

もし `find` コマンドで取得したファイル名の中に Document 2026.txt のように半角スペースが含まれていた場合、for ループは「Document」と「2026.txt」という2つのファイルに分割して処理してしまいます。

これは、シェルがループデータを区切る際に、改行だけでなくスペースやタブも区切り文字(環境変数 IFS:Internal Field Separator)としてデフォルトで認識しているためです。

この問題を回避するためには、ループを実行する直前で IFS を「改行のみ」に一時的に設定します。

# 現在のIFSをバックアップ
SAVE_IFS="$IFS"

# IFSを改行のみに設定
IFS=$'\n'

for file in $(find . -name "*.txt"); do
    echo "スペース入りのファイル名も安全に処理: $file"
done

# IFSを元に戻す
IFS="$SAVE_IFS"

※ なお、コマンド結果を行ごとに1行ずつ安全に処理したい場合は、for 文ではなく while read がおすすめです(これについては今後の記事で解説します)。

指定回数だけループを回す(C言語風の書き方)

「1から10まで数字を増やしながら繰り返したい」という場合、シェルスクリプトではC言語風の直感的な for 構文が使えます。

# 1から10まで繰り返す(iを1ずつ増やす)
# 評価には丸括弧を二重にした (( )) を使用します

for ((i = 1; i <= 10; i++)); do
    echo "カウント: $i"
done

インデックス(カウンタ変数)の数字を直接計算やファイル名の一部(例: backup_01.tar など)に流用したい場合に非常に便利です。

ループの制御:break と continue

他のプログラミング言語と同様に、シェルスクリプトのループでも、途中でループ自体を抜け出す break と、次の周回へスキップする continue が使用できます。

# break の例:特定の条件で繰り返しを終了する
for num in 1 2 3 4 5; do
    if [ "$num" -eq 4 ]; then
        break # 4 になったらループを完全に脱出する
    fi
    echo "数値: $num"
done

# continue の例:特定の処理をスキップして次の周へ進む
for file in /tmp/app/*; do
    if [ -d "$file" ]; then
        continue # ディレクトリだった場合は何もせず次のファイルへ
    fi
    echo "ファイルを処理中: $file"
done

まとめ

実務でシェルスクリプトを記述する場合、「ファイル名のスペース」や「対象が存在しない(空振り)」といった実行環境による変化への配慮が不可欠です。

今回解説した shopt -s nullglob の活用や IFS の一時切り替えを適用しておくことで、想定外の入力に対してもエラーで停止しないスクリプトが書けるようになります。ぜひ実践に取り入れてみてください!

コメント

タイトルとURLをコピーしました