ループは Bash スクリプトの心臓部です。ところが実務では、continue を場当たり的に使って“とりあえず先へ進む”うちに、後始末が抜けたり、終了コードが伝わらずに不具合が潜る――そんな崩れ方が起きがちです。本稿は既存の「continue」解説を土台に、break の正しい使いどころと終了コード設計まで含めて、ループの“出口”を意図どおりに制御するための指針をまとめます。
対象は for/while/until。スキップ(continue)と脱出(break)、そして関数の return/スクリプトの exitの役割分担をはっきりさせ、ネスト時の continue N/break N、ストリーム処理の while read -r で起きやすい罠、後始末の一元化までを、短いコード例と設計の意図で解説します。
環境は Bash 5 系、set -euo pipefail 前提。目標はシンプルです。「何をスキップし、いつ中断し、どの失敗をどこへ返すか」をあらかじめ決め、コードに落とし込めるようになること。読み終える頃には、continue を“失敗の隠蔽”にせず、break と終了コードで意図が外に伝わるループを書けるようになります。
- continueの基本構文(最初に押さえること)
- まずは出口の整理:continue/break/return・exit
- continue の基本:弱い失敗は“記録して流す”
- break の基本:目的達成・上限到達で“きれいに抜ける”
- return と exit:どこに結果を返すのか
- ストリーム処理:while read -r とパイプの罠
- 失敗の設計:終了コードをどう決めて、どう伝えるか
- ミニ実践:continue と break を組み合わせる
- よくあるエラー:continue: only meaningful in a `for’, `while’, or `until’ loop
- よくある質問(FAQ)
- まとめ
- 関連記事
continueの基本構文(最初に押さえること)
continue は、for・while・until ループの中で使う組み込みコマンドです。実行されるとそれ以降の処理をスキップして、次の周回(イテレーション)へ進みます。ループそのものを終了する break とは異なり、あくまで「今回だけ飛ばす」点がポイントです。
continue [N]
最も基本的な例として、1〜5のうち偶数だけをスキップして表示するスクリプトを見てみましょう。
for i in 1 2 3 4 5; do
if (( i % 2 == 0 )); then
continue # 偶数はスキップして次のiへ
fi
echo "$i"
done
# 出力: 1 3 5
while ループでも同様に使えます。
i=0
while (( i < 5 )); do
((i++))
[[ $i -eq 3 ]] && continue # 3のときだけスキップ
echo "$i"
done
# 出力: 1 2 4 5
continueとbreakの違い
| コマンド | 動作 | 使いどころ |
|---|---|---|
continue | 今回の処理だけスキップして次の周回へ | 不要なデータを無視して処理を続けたいとき |
break | ループ自体をその場で終了する | 目的を達成した/これ以上続ける必要がないとき |
ネストしたループでの continue N
continue に数値を指定すると、内側から数えて N 番目の外側のループへスキップできます。continue 2 なら、2つ外側のループの次の周回に進みます。
for i in 1 2 3; do
for j in a b c; do
[[ "$j" == "b" ]] && continue 2 # 外側ループの次のiへ直接スキップ
echo "$i-$j"
done
done
# 出力: 1-a 2-a 3-a
ここまでが continue の基本です。ここから先は、実務のスクリプトで continue を安全に使うための設計――break との役割分担、後始末、終了コードの伝え方――を掘り下げます。
まずは出口の整理:continue/break/return・exit
continue は「現在の1回分だけスキップして、次の反復へ進む」ふるまいです。break は「いま居るループ(必要なら外側も含めて)から脱出する」ふるまいです。
関数の中なら return、スクリプト全体を終えるなら exit を使い、どこに結果(終了コード)を返すかを決めます。
# 例:1,2,skip,3 を処理。skip は飛ばし、3 で目的達成 → 脱出
set -euo pipefail
nums=(1 2 "skip" 3 4)
for n in "${nums[@]}"; do
[[ "$n" == "skip" ]] && continue # スキップ
if (( n == 3 )); then
echo "見つけたので脱出"
break # 脱出
fi
echo "処理: $n"
done
continue の基本:弱い失敗は“記録して流す”
「検証エラーはログに残して処理は続ける」という方針なら continue が向きます。“握りつぶさない” のがコツです。件数や最初のエラー原因を変数に記録し、ループの外でまとめて判断します。
set -euo pipefail
invalid=0
while IFS= read -r line; do
[[ -z "$line" || "$line" == \#* ]] && continue # 空行・コメントは弱い無視
if ! printf '%s\n' "$line" | grep -qE '^[a-z0-9_-]{3,}$'; then
((invalid++))
printf 'WARN: 無効な名前: %s\n' "$line" >&2
continue
fi
echo "OK: $line"
done < <(printf '%s\n' "alice" "" "#comment" "too-long-&&" "bob")
# ループ後にまとめて判断
if (( invalid > 0 )); then
echo "無効行: $invalid 件" >&2
fi
continue を使うと後始末が飛びがちなので、後片付けは trap で一元化しておくと安心です。
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT # continue でも break でも必ず実行される
break の基本:目的達成・上限到達で“きれいに抜ける”
「最初の成功で十分」「一定回数で打ち切る」など、処理を中断する条件がはっきりしているときは break が合います。見つけた結果を変数に保存してから抜けると、ループ外の処理が書きやすくなります。
set -euo pipefail
found=""
for host in 10.0.0.{1..10}; do
if ping -c1 -W1 "$host" >/dev/null 2>&1; then
found="$host"
break
fi
done
if [[ -n "$found" ]]; then
echo "最初に応答したホスト: $found"
# ここで found を使う処理 …
else
echo "応答なし" >&2
exit 1
fi
ネストしたループでは break 2 のように段数を指定できます。ただし深くなるほど読みづらくなるので、関数に切り出して return で早期退出する方が保守しやすいことが多いです。
search() {
local target="$1"
local i j
for i in {1..3}; do
for j in {a..c}; do
[[ "$i$j" == "$target" ]] && { printf '%s\n' "$i$j"; return 0; }
done
done
return 1
}
if result="$(search "2b")"; then
echo "見つかった: $result"
else
echo "見つからない" >&2
fi
return と exit:どこに結果を返すのか
関数の成否は return の終了コードで返し、呼び出し元で判断します。スクリプト全体の成否は exit で返します。ループの中から直接 exit してしまうと、片付けやログが飛ぶことがあるため、基本は break→外で判断→必要なら exit の順がおすすめです。
process_item() {
local x="$1"
[[ "$x" =~ ^[0-9]+$ ]] || { echo "NG:$x" >&2; return 1; }
echo "OK:$x"
}
final=0
for x in "42" "oops" "7"; do
if ! process_item "$x"; then
final=1 # 失敗は記録して続ける
continue
fi
done
exit "$final" # 最終結果として一度だけ外へ返す
ストリーム処理:while read -r とパイプの罠
… | while read の形は、多くの環境で while がサブシェルになり、ループ内で更新した変数が外へ戻りません。回避策として プロセス置換 < <(...) を使うと安全です。
set -euo pipefail
count=0
# 悪い例: 変数が外に反映されない可能性
# printf '%s\n' a b c | while read -r s; do ((count++)); done
# echo "$count" # 0 のまま…
# 良い例: プロセス置換で同一シェル内
while IFS= read -r s; do
((count++))
done < <(printf '%s\n' a b c)
echo "件数: $count" # 3
失敗の設計:終了コードをどう決めて、どう伝えるか
終了コードは「0=成功、非0=失敗」が約束事です。ループや複数処理のときに大切なのは、どんなポリシーで最終結果を決めるかを先に決めることです。例として三つの考え方を示します。
「全件成功なら0」
どれか1つでも失敗したら非0にする、もっとも厳格な判定です。
final=0
for path in "${files[@]}"; do
if ! do_task "$path"; then
final=1
fi
done
exit "$final"
「最初の失敗コードを返す」
最初に起きた失敗の種類(終了コード)を外へ伝えたいときに向きます。
first_fail=0
for path in "${files[@]}"; do
if ! do_task "$path"; then
first_fail=$? # 0 以外が入る
break # ここで打ち切る方針もあり
fi
done
exit "$first_fail"
「1件でも成功したら0」
フェイルオーバーや候補列の試行で、成功が一つでもあれば OK とする判定です。
ok=1
for endpoint in "${candidates[@]}"; do
if try_connect "$endpoint"; then
ok=0
break
fi
done
exit "$ok"
ミニ実践:continue と break を組み合わせる
入力データの検証エラーはスキップしつつ、目的が達成できたら脱出、最後に結果を一度だけ返す流れです。
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
target="admin"
found=""
invalid=0
while IFS= read -r user; do
[[ -z "$user" || "$user" == \#* ]] && continue
if ! [[ "$user" =~ ^[a-z][a-z0-9_-]{2,}$ ]]; then
((invalid++))
printf 'WARN: 無効ユーザー: %s\n' "$user" >&2
continue
fi
if [[ "$user" == "$target" ]]; then
found="$user"
break
fi
done < users.txt
if [[ -n "$found" ]]; then
echo "見つかった: $found"
exit 0
fi
printf 'INFO: 見つからず(無効行 %d 件)\n' "$invalid" >&2
exit 1
この形だと、continue は「弱い失敗の整理」、break は「目的達成の早期終了」、最終の exit は「外へ結果を1回だけ伝える」という役割分担になります。
後始末が必要なら、先に触れたように trap を仕掛けておくと、どの出口を通ってもクリーンに終われます。
よくあるエラー:continue: only meaningful in a `for’, `while’, or `until’ loop
continue をループの外や、if だけのブロック(ループではない)で実行すると、次のエラーになります。
bash: continue: only meaningful in a `for', `while', or `until' loop
典型的な原因は、パイプの右側で while がサブシェルとして実行され、期待した位置にループが存在しないケースや、条件分岐 (if) だけで continue を使ってしまうケースです。
# NG: if だけでは continue は使えない(ループが無い)
if true; then
continue # エラー
fi
# OK: continue はループの中でのみ使う
for i in 1 2 3; do
if [[ "$i" == 2 ]]; then
continue
fi
echo "$i"
done
関数の中で「早期に処理を打ち切りたい」だけであれば、continue ではなく return を使います。ループと関数の役割を混同しないことが、このエラーを避ける一番の近道です。
よくある質問(FAQ)
Q1. bashのcontinueとは何ですか?
for・while・until ループの中で、それ以降の処理をスキップして次の周回に進む組み込みコマンドです。ループ自体を終了する break とは異なり、あくまで今回の1回分だけを飛ばします。
Q2. for文でcontinueを使うにはどう書きますか?
for i in 1 2 3 4 5; do
[[ $((i % 2)) -eq 0 ]] && continue
echo "$i"
done
# 出力: 1 3 5
Q3. 「continue: only meaningful in a…」エラーが出るのはなぜですか?
continue をループの外や if だけのブロックで実行すると発生します。詳しくは記事内の「よくあるエラー」セクションを参照してください。
Q4. continueとbreakはどう使い分けますか?
continue は「不要なデータだけ無視して処理を続けたい」ときに、break は「目的を達成したのでループ自体を終えたい」ときに使います。両者の使い分けは記事内の比較表を参照してください。
まとめ
continue は“先へ進むためのスキップ”、break は“ここで終えるための脱出”です。そして return と exit は「どこへ結果を返すか」を決めるスイッチです。
まずは、何をスキップし、いつ中断し、どの失敗をどこへ返すかを言葉で決めてからコードに落とし込んでみてください。trap による後始末の一元化、while read -r とプロセス置換の組み合わせ、集計変数による終了コード設計――この三点を押さえるだけで、ループはぐっと壊れにくく、読みやすくなります。
