導入
「サーバー一覧のファイルを読んで、1台ずつ確認する」「CSVを1行ずつ処理する」。実務のシェルスクリプトで最も出番が多い形が、この while read です。1つの型として、まるごと覚えてしまいましょう。
説明
型はこれです。
#!/bin/bash
while read line; do
echo "サーバー: $line"
done < servers.txt
ポイントは最後の done < ファイル名。「このループへの入力は、このファイル」という指定です。read が1行ずつ取り出し、読む行が尽きると終了ステータスが 0 以外になって、ループが自然に終わります。
flowchart TD
A["servers.txt"] --> B["while read line"]
B --> C{"読めた?"}
C -->|"読めた"| D["line に1行入る → 中身を実行"]
D --> B
C -->|"もう無い"| E["done(終了)"]
read は複数の変数に分けて受け取ることもできます。空白区切りの列を、名前付きで扱えます。
while read ip method path status; do
echo "$status : $path"
done < logs/access.log
logs/access.log は「IP メソッド パス ステータス」の並びなので、4つの変数に自然に分かれます。cut -d ' ' -f 4 と書くより、ずっと意図が読めるコードになります。
なぜ cat ファイル | while read line ではなく done < ファイル なのか。パイプで渡すとループが別のプロセスで動き、ループ内で変えた変数がループの外に残らないという落とし穴があるからです。done < ファイル なら、その心配がありません。
ファイル1行ずつの処理は
for line in $(cat file)でも書けそうに見えますが、やってはいけません。行ではなく空白で区切られてしまい、スペースを含む行が壊れます。1行ずつ読むならwhile read。これは定石です。
やってみよう
スクリプトを実行して、servers.txt の3行が1つずつ処理されることを確かめましょう。次に done < servers.txt を done < logs/access.log に変え、read line を read ip method path status にして、echo "$status : $path" を出してみてください。列に名前がつくと読みやすさが変わります。
演習
logs/access.log を while read で1行ずつ読み、各行のステータス(4列目)とパス(3列目)を ステータス: 403 のような形で出してください(403 が含まれていればOK)。
ヒント1を見る
while read ip method path status; do … done < logs/access.log
ヒント2を見る
ループの中は echo "ステータス: $status"