強力なループ戦略:Bashスクリプトでのファイルとリストの反復処理

essential Bash のループ技術(`for` および `while` を使用)を習得し、反復的なシステムタスクを効率的に自動化します。この包括的なガイドでは、リストの反復処理、数値シーケンスの処理、および `while IFS= read -r` のようなベストプラクティスを使用したファイルを行ごとに確実に処理する方法を網羅しています。強力で信頼性の高いシェルスクリプティングと自動化のために、基本的な構文、高度なループ制御(`break`、`continue`)、および不可欠なテクニックを、実践的なコード例と共にご紹介します。

38 ビュー

強力なループ戦略:Bashスクリプトでのファイルとリストの反復処理

Bashループは、シェルスクリプトにおける自動化のエンジンです。ディレクトリ内のすべてのファイルを処理する必要がある場合でも、指定された回数だけタスクを実行する必要がある場合でも、設定データを一行ずつ読み取る必要がある場合でも、ループは反復的な操作を効率的に処理するために必要な構造を提供します。

スクリプトを堅牢でスケーラブル、かつインテリジェントにするためには、主に2つのBashループ構造、forwhileを習得することが不可欠です。本ガイドでは、リスト、ファイル、ディレクトリの反復処理やシーケンス生成のための基本的な構文、実用的なユースケース、ベストプラクティスを探り、自動化ワークフローを強化します。


forループ:固定セットの反復処理

forループは、処理する必要のある項目のコレクションが事前に分かっている場合に最適です。このコレクションは、明示的な値のリスト、コマンドの結果、またはグロブによって見つけられた一連のファイルである可能性があります。

1. 標準リストの反復処理

最も一般的な使用例は、スペースで区切られた項目のリストを反復処理することです。

構文

for VARIABLE in LIST_OF_ITEMS; do
    # $VARIABLE を使用したコマンド
done

例:ユーザーリストの処理

# 処理するユーザーのリスト
USERS="alice bob charlie"

for user in $USERS; do
  echo "$user のホームディレクトリを確認中..."
  if [ -d "/home/$user" ]; then
    echo "$user はアクティブです。"
  else
    echo "警告: $user のホームディレクトリが見つかりません。"
  fi
done

2. C言語スタイルの数値反復処理

カウントや特定の数値シーケンスが必要なタスクのために、BashはC言語スタイルのforループをサポートしています。これは、ブレース展開またはseqコマンドと組み合わせて使用​​されることがよくあります。

構文(C言語スタイル)

for (( INITIALIZATION; CONDITION; INCREMENT )); do
    # コマンド
done

例:カウントダウンスクリプト

# 5回ループする (iは1から始まり、iが5以下である間続く)
for (( i=1; i<=5; i++ )); do
  echo "イテレーション番号: $i"
  sleep 1
done
echo "完了!"

代替:単純なシーケンスのためのブレース展開の使用

ブレース展開は、連続する整数やシーケンスを生成する際に、seqを使用するよりもシンプルで高速です。

# 10から1まで数値を生成する
for num in {10..1}; do
  echo "カウントダウン: $num"
done

3. ファイルとディレクトリの反復処理(グロブ)

forループ内でワイルドカード(*)を使用すると、特定のパターンに一致するファイル(例:すべてのログファイルやディレクトリ内のすべてのスクリプト)を処理できます。

例:ログファイルのアーカイブ

ファイル名(特にスペースや特殊文字を含むファイル名)を扱う場合は、変数("$file")を引用符で囲むことが非常に重要です。

TARGET_DIR="/var/log/application"

# ターゲットディレクトリ内の.logで終わるすべてのファイルをループ処理
for logfile in "$TARGET_DIR"/*.log; do

  # ファイルが実際に存在するかどうかを確認する(一致するファイルがない場合にリテラル "*.log" を処理するのを防ぐため)
  if [ -f "$logfile" ]; then
    echo "$logfile を圧縮中..."
    gzip "$logfile"
  fi
done

whileループ:条件に基づく実行

whileループは、指定された条件が真である限り、一連のコマンドのブロックの実行を継続します。これは、入力ストリームの読み取り、条件の監視、または反復回数が不明なタスクの処理によく使用されます。

1. 基本的なwhileループ

構文

while CONDITION; do
    # コマンド
done

例:リソースを待機する

このループは、testコマンド([ ])を使用して、続行する前にディレクトリが存在するかどうかを確認します。

RESOURCE_PATH="/mnt/data/share"

while [ ! -d "$RESOURCE_PATH" ]; do
  echo "リソース $RESOURCE_PATH のマウントを待機中..."
  sleep 5
done

echo "リソースが利用可能です。バックアップを開始します。"

2. 堅牢なwhile readパターン

whileループの最も強力な用途は、ファイルまたは出力ストリームの内容を一行ずつ読み取ることです。このパターンは、catの出力に対してforループを使用するよりもはるかに優れており、スペースや特殊文字を確実に処理できます。

ベストプラクティス:一行ずつの読み取り

最大限の堅牢性を確保するために、次の3つの主要コンポーネントを利用します。
1. IFS=: 内部フィールドセパレータ(IFS)をクリアし、先頭/末尾のスペースを含む行全体が変数に読み込まれるようにします。
2. read -r: -rオプションはバックスラッシュの解釈を防ぎます(生読み取り)。これはパスや複雑な文字列にとって不可欠です。
3. 入力リダイレクト(<: ファイルの内容をループにリダイレクトし、ループが現在のシェルコンテキストで実行されるようにします(サブシェル問題を回避します)。

# データが含まれ、一行に1つのアイテムがあるファイル
CONFIG_FILE="/etc/app/servers.txt"

while IFS= read -r server_name; do

  # 空行またはコメント行をスキップする
  if [[ -z "$server_name" || "$server_name" =~ ^# ]]; then
    continue
  fi

  echo "サーバー $server_name にpingを実行中"
  ping -c 1 "$server_name"

done < "$CONFIG_FILE"

ヒント:ループでのcatの回避

ファイルを読み取る際にcat file | while read line; do...を使用しないでください。パイプ処理はサブシェルを作成するため、ループ内で設定された変数はループ終了時に失われます。代わりに、入力リダイレクトパターン(while ... done < file)を使用してください。

高度なループ制御とテクニック

効果的なスクリプトには、実行時の条件に基づいてループの実行を制御する機能が必要です。

1. フロー制御:breakcontinue

  • break: 残りの反復処理や条件に関係なく、ループ全体を直ちに終了します。
  • continue: 現在の反復処理をスキップし、直ちに次の反復処理(またはwhile条件の再評価)にジャンプします。

例:検索と停止

SEARCH_TARGET="target.conf"

for file in /etc/*; do
  if [ -f "$file" ] && [[ "$file" == *"$SEARCH_TARGET"* ]]; then
    echo "ターゲット構成を次に見つけました: $file"
    break  # 見つかったら処理を停止する
  elif [ -d "$file" ]; then
    continue # ディレクトリはスキップし、ファイルのみチェックする
  fi
  echo "ファイルを確認中: $file"
done

2. IFSを使用した複雑な区切り文字の処理

一行ずつのファイル読み取りではIFSをクリアする必要がありますが、異なる文字(コンマなど)で区切られたリストを反復処理するには、IFSを一時的に設定する必要があります。

CSV_DATA="data1,data2,data3,data4"
OLD_IFS=$IFS # 既存のIFSを保存する
IFS=','       # IFSをコンマ文字に設定する

for item in $CSV_DATA; do
  echo "見つかった項目: $item"
done

IFS=$OLD_IFS # ループ直後に既存のIFSを復元する

警告:グローバルなIFSの変更

スクリプト内でIFSを変更する前に、必ず既存の$IFSを保存してください(例:OLD_IFS=$IFS)。元の値を復元しないと、後続のコマンドで予期せぬ動作を引き起こす可能性があります。

堅牢なBashループのためのベストプラクティス

プラクティス 理由
変数は常に引用する 単語分割やグロブ展開を防ぐために"$variable"を使用します。特にファイル反復処理の場合。
while IFS= read -rを使用する ファイルを一行ずつ処理するための最も信頼性の高い方法で、スペースや特殊文字を正しく処理します。
存在チェックを行う グロブ処理(*.txt)を使用する場合は、一致するファイルがない場合にループがリテラルなパターン名を処理しないように、必ずチェック(if [ -f "$file" ];)を含めます。
変数をローカル化する 関数内でlocalキーワードを使用し、ループ変数が誤ってグローバル変数を上書きするのを防ぎます。
外部コマンドよりもビルトインを使用する パフォーマンスのために、seqのような外部コマンドを起動するよりも、ブレース展開({1..10})やC言語スタイルのループを使用します。

結論

forwhileループはBashスクリプトの基本であり、最小限のコード繰り返しで複雑な自動化タスクを可能にします。ファイル処理のためのwhile IFS= read -rアプローチや、forループでの厳格な引用符の使用など、堅牢なパターンを継続的に適用することで、予期しないデータ形式に耐性があり、信頼性が高く、パフォーマンスの高いスクリプトを構築し、シェル自動化の取り組みに真の力を発揮できます。