現場で使えるシェルスクリプト入門11:while文の基本と『while read』による安全な1行処理

シェルスクリプト IT

前回の記事(入門10)では、for 文の高度な使い方や、ファイルスペースによって単語が分解されてしまうIFSの罠について解説しました。

今回は、「繰り返し処理」のもう一つの主役である「while文」について解説します。特に、前回のfor文の解説で『ファイルを安全に行ごとに処理するのにおすすめ』と触れていた「while read」構文について、その理由と使い方を詳しく解説します。

大量のテキストファイルを1行ずつ処理する鉄板パターンを習得しましょう!

while文の基本ルールと構文

while 文は、「指定した条件が真(True)である間処理を繰り返す」というループ制御構文です。

while 条件; do
    # 条件が真(成功)である限り繰り返す処理
done

条件の部分には、第8回で学んだ [ ][[ ]] のテストコマンド、あるいは通常のコマンドなどを記述します。

💡 シンプルなカウントアップループ

count=1
while [ "$count" -le 5 ]; do
    echo "カウント: $count"
    count=$((count + 1))
done

※ カウンタ変数(count)のインクリメント処理を忘れると、ループが永遠に終わらない「無限ループ」になり、サーバー負荷が上昇するので注意してください。

「while read」による安全な1行処理

前回の for ループでは、for line in $(cat list.txt) のように記述すると、ファイル内の行に「半角スペース」が含まれていた場合に行がバラバラに分解されてしまう致命的な問題がありました。

これを防ぎ、「ファイルから1行ずつ、スペースを維持したまま安全にデータを読み込む」ための構文が while read です。

実際に動作を確認してみましょう。
まずは、スペースを含むlist.txtを用意します。以下はlist.txtの内容です。

行番号: 1
行番号: 2
行番号: 3
行番号: 4
行番号: 5

このファイルをwhile read文を使って読み込みます。

# ファイルから1行ずつ読み込んで処理する基本形
# -r オプションはバックスラッシュ(\)をエスケープ文字として扱わないための安全策です

while read -r line; do
    echo "1行取得: $line"
done < list.txt

実行結果:

1行取得: 行番号: 1
1行取得: 行番号: 2
1行取得: 行番号: 3
1行取得: 行番号: 4
1行取得: 行番号: 5

ループを閉じる done の後ろに < ファイル名 を指定してリダイレクト(流し込み)することで、ファイルの中身を1行ずつ読み取ります。スペースやタブが含まれていても行が崩れる心配はありません。

コマンド出力をパイプで while read に渡す方法

ファイルから直接読み込むだけでなく、別のコマンドの実行結果をパイプ(|)で while read に直接渡すこともできます。

# 例: /var/log以下の .log ファイル一覧を1行ずつ処理する
find /var/log/ -name "*.log" | while read -r logfile; do
    echo "ログを解析中: $logfile"
done

⚠️ 落とし穴:パイプを使うと変数のスコープが変わる

パイプで while ループを繋ぐ場合、シェルスクリプトならではの「変数の落とし穴」が存在します。

# ファイル数をカウントしたい例
total_count=0

find /var/log/ -name "*.log" | while read -r logfile; do
    total_count=$((total_count + 1))
done

# この total_count はいくつになるでしょうか?
echo "トータル件数: $total_count"

答えは、何件ログファイルが見つかろうとも、「0」と表示されます。

# ./test1.sh 
トータル件数: 0

なぜなら、パイプの右側に書いたコマンド(while ループ)は、現在のシェルとは別のプロセス(サブシェル)で実行されるためです。ループの中で total_count を増やしても、それは別プロセスの中の変数であり、ループを抜けたメインプロセス側の変数には反映されません。

💡 解決策:プロセス置換を使う

ループ内で増やした変数の値をメインプロセス側でも維持したい場合は、パイプを使うのをやめ、プロセス置換(<(コマンド)を使ってファイルを流し込む形式で記述します。プロセス置換とは、コマンドの実行結果を一時的にファイルのように扱う仕組みです。これならサブシェルが起動しないため、正常に変数を引き継げます。

total_count=0

# パイプを使わず、プロセス置換で while に流し込む
while read -r logfile; do
    total_count=$((total_count + 1))
done < <(find . -name "*.log")

# 今度は正しくカウントされた結果が表示されます
echo "トータル件数: $total_count"

実行結果:

# ./test1.sh 
トータル件数: 17

for文とwhile文の使い分け基準

最後に、ループを実装する際の使い分けの整理です。

構文適した用途特徴
for文配列の全要素のループ、定数リストの順次処理、指定回数(C言語風)の繰り返しあらかじめ処理する数(回数や要素数)が決まっている場合にコードが簡潔になる。
while文ファイルやコマンド出力結果の「行ごと」の処理、条件が満たされている間の繰り返しスペースを維持した安全な行読み込み(while read)ができる。

まとめ

while 文は、ファイルやコマンド出力を安全かつ正確にさばくために、よく使われる必須のテクニックです。

パイプと組み合わせた際の「サブシェルによる変数スコープの落とし穴」など、最初につまずきやすいポイントさえ押さえておけば、他人の書いたスクリプトを読む際にも理解が深まります。ぜひ使いこなせるようになりましょう!

コメント

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