ループ処理 for, while
ファイルの数だけ、同じ行を書き足している
ログを 1 本ずつ確認するスクリプトを書くとき、ファイルが 3 本なら同じ行を 3 回貼れば済みます。ところが翌月には 12 本に増えていて、貼り忘れた 1 本だけ確認から漏れます。数が変わっても書き換えなくてよい形にします。
for は、並んだものを 1 つずつ変数に入れて、do から done までを繰り返します。
ターミナル
for f in /var/log/nginx/*.log; do
echo "--- $f"
tail -1 "$f"
doneワイルドカードの展開結果がそのまま並びになるので、ファイルが増えてもスクリプトは触りません。ファイル名に空白が混じることを考えて、"$f" の囲みは必ず付けます。
並びは自分で書いても構いません。連番なら for month in {1..12} のように範囲で書けます。
回数ではなく、条件で回りたい
相手のサーバーが起き上がるのを待つような処理は、何回で終わるかが決まっていません。こういうときは while を使います。条件が成り立つあいだ繰り返します。
ターミナル
tries=0
while ! curl -sf https://example.com/health > /dev/null; do
tries=$((tries + 1))
if [[ "$tries" -ge 5 ]]; then
echo "5 回試しましたが応答がありません"
break
fi
sleep 3
done! は成否を裏返す記号で、ここでは「届かないあいだ繰り返す」と読みます。$((tries + 1)) は計算の書き方です。シェルの変数は文字として扱われるため、足し算をするときはこの形で囲みます。break はループを途中で抜ける命令です。
1 行ずつ処理したいのに、まとめて渡ってしまう
ファイルの中身を 1 行ずつ扱いたいとき、変数に読み込んでから for に渡すと、空白でも切れてしまって行の単位が崩れます。行を単位にするなら while read を使います。
ターミナル
while read -r host; do
echo "check ${host}"
ping -c 1 "$host" > /dev/null && echo " ok"
done < hosts.txtdone のうしろの < hosts.txt で、読み込む先を指定しています。-r は行の中のバックスラッシュをそのまま文字として扱う指定で、付けておくのが安全です。
飛ばしたい行があるときは continue を使うと、そこから先を実行せずに次の回に進みます。
ターミナル
for f in ./*.log; do
if [[ ! -s "$f" ]]; then
continue
fi
gzip "$f"
done-s は「中身が空でない」という条件です。空のログを圧縮しても意味がないので、先に飛ばしています。
課題
- for ループを使う
- Number: 1, Number: 2, Number: 3 の順で出力する