loopは同じ処理を繰り返す構文ですが、対象列挙、終了条件、error時の継続、並行実行、file名の安全な受渡しを設計しないと誤処理を拡大します。for、while、until、select、break、continueを目的別に選び、上限とdry-run表示を先に置きます。
結論は「既知listはfor、line入力はwhile IFS= read -r、条件待ちは上限付きwhile/untilを使います。対象件数をpreviewし、一件failureで止めるか続けるかを明示します」です。
loopへ渡す母集団を先に固定する
Bashのfor name in wordsは展開後のwordを順に処理し、whileはtest commandが成功する間、untilは失敗する間繰り返します。pipelineのwhile loopはsubshellになる構成があり、loop内variableがparentへ残らない場合があります。read -rとIFS=でlineを壊さず読みます。
- loop対象の件数と最大上限
- 一件の成功・skip・failure条件
- file名やinputのseparator
- 途中停止・再開・idempotency
- loop後に必要なsummaryとexit status
実行前に対象をprintし、想定件数を超えたら停止します。wildcardは0件時にliteralのまま残るshell option差があるため存在testを入れます。file名を扱うときfor f in $(ls)を使わず、glob、array、find -print0等を選びます。
loopへ渡る集合を先に表示し、件数、順序、separator、0件時の挙動を確認します。file名を扱うならspace、newline、glob文字、leading dashを含む可能性を考え、for f in $(ls)を使いません。while条件の終了性、最大反復数、外部APIのrate limit、一件失敗時にstop・skip・retryのどれにするかを決めます。loop後に必要なsummaryとexit statusも定義します。
一件sampleから件数を増やす
最小fixtureで1件、0件、複数件、failureを作ります。loop bodyをfunction化し、一件ごとのinput、status、durationを記録します。変更系ならdry-runとbackupを用意し、最初のbatchを少数に限定します。retryには回数とsleep上限を設けます。
既知値をforで処理
for env in dev stage prod; do
printf 'env=%s\n' "$env"
done
word listをsource内で明示し、untrusted textを再分割しません。
globでfileを安全に列挙
for file in ./logs/*.log; do
[ -e "$file" ] || continue
printf '%s\n' "$file"
done
spaceを含むfile名をquoteし、0件時のliteral globをskipします。
lineをwhile readで処理
while IFS= read -r line || [ -n "$line" ]; do
printf '%s\n' "$line"
done < ./input.txt
backslashと末尾newlineなしの最終lineを扱います。
回数上限付きretry
attempt=1
while [ "$attempt" -le 3 ]; do
check_service && break
attempt=$((attempt + 1))
done
無限retryにせず、最終failureをcallerへ返す処理を追加します。
continueとfailure count
failures=0
for item in one two three; do
process_item "$item" || { failures=$((failures + 1)); continue; }
done
printf 'failures=%s\n' "$failures"
continue方針を明示し、failure 0件だけをsuccessにします。
固定listはquoted arrayをforで回し、line入力はwhile IFS= read -r lineを使います。find結果のfile名を安全に渡すなら-print0とread -d ”の組み合わせを検討します。while条件へcounterと上限を設け、外部commandは一件のdry-runから始めます。変更を伴う処理は対象一覧をversion付きfileへ保存し、success・skip・failureを個別に記録します。
for・while・until・subshellとword splittingを理解する
breakは現在または指定levelのloopを抜け、continueは次iterationへ進みます。loop bodyの最後のstatusがloop全体statusへ影響しますが、複数failureを集計するなら明示counterが必要です。pipeline loopのsubshell、command substitutionのnewline削除、unquoted expansionはstateやdataを壊します。
- forは展開されたword listを順に処理する
- whileはcondition成功中、untilはcondition失敗中に繰り返す
- read -rはbackslashをescapeとして消費しない
- breakとcontinueはnumeric levelを指定できる
- pipeline内loopはshell設定によりsubshell contextで動く場合がある
unquoted command substitutionは結果をword splittingとfilename expansionへ通すため、file名境界を壊します。pipelineの末尾にwhile loopを置くとsubshellで実行され、loop内の変数変更が親shellへ残らない構成があります。process substitutionなら親shellでloopを動かせる場合があります。break、continue、return、exitは抜ける範囲が異なり、nested loopではlevel指定もtestが必要です。
無限loopと大量変更を防ぐ
loopへdelete、permission、network request等の変更を入れる場合、対象上限、rate limit、停止file、rollbackを設けます。production全件を最初のrunにせずcanaryを使います。failureをechoだけで隠さず、最終statusと未処理listを保存します。
- for item in $(cat file)で空白を壊す
- 0件globをliteral filenameとして処理する
- retryへ上限を付けない
- pipeline loop内のcounterをparentで期待する
- 一部failureがあってもstatus 0を返す
削除、権限変更、送信、再起動をloopへ入れる前に対象上限、確認mode、idempotency、途中再開、rollbackを設計します。外部serviceへのretryは指数backoff、最大回数、jitter、Retry-Afterを考慮し、無限送信を避けます。一件のpath失敗で変数が空になったときrootやcurrent directoryへ作用しないよう、必須値とprefixを各反復で検証します。
全件・一部failureを集計する
0件、1件、space/newline file名、body failure、timeout、interrupt、再実行をtestします。processed/skipped/failed件数の合計が母集団と一致し、途中停止後の再開で重複変更しないことを確認します。
- 母集団と上限を実行前に表示した
- 0件・一件failure・途中停止をtestした
- quoteとseparatorでinput境界を保った
- summary countと最終statusが処理結果を表す
0件、1件、通常件数、上限超過、space/newlineを含む値、途中failure、signal、再実行をfixtureで試します。入力件数とsuccess・skip・failureの合計を一致させ、処理順、重複、欠落を確認します。loop後の変数が必要ならsubshell境界をtestし、途中停止後に同じ入力で再開して二重処理や破損がないか確認します。
shell loopとworkflow engineを使い分ける
固定少数はfor、line streamはwhile read、条件待ちは上限付きwhile/untilを使います。大量dataや並列処理、transactionが必要ならshell loopからworkflow engineやapplication codeへ移します。
件数が小さく逐次処理で十分なら読みやすいfor/whileを使います。大量処理や並列化が必要でもxargs -P等へ即移行せず、順序、排他、rate limit、ログ、failure集約を設計します。依存関係のあるjob、長時間workflow、分散retryはshell loopへ抱え込まず、schedulerやqueue、専用orchestratorを使います。

コメント