Bashのfor文は、リストや範囲の反復処理に欠かせない基本中の基本。
しかし、よくある罠やちょっとした安全策を知らないと、思わぬバグやパフォーマンス低下を招く可能性があります。
本稿では、「安全・高速・読みやすさ」の3点にこだわって、Bash道流にガチ整理しました。
for 以外にもBashに関するテクニックに関しては 「Bash」ページにまとめています。
- 即コピペ例(安全テンプレ)
- Bash の for は3種類(最短の使い分け)
- for と while の使い分け(1分で判断)
- 実務に役立つサンプル集 14選
- 1) 配列を安全に回す(空要素・空白対応)
- 2) 配列のインデックス+要素を同時に
- 3) 連想配列(Bash 4+)
- 4) グロブでファイルを回す(未一致安全:nullglob)
- 5) ディレクトリだけ回す(状態汚染を避ける:サブシェル)
- 6) 拡張子変換(jpg → webp)
- 7) 数値レンジ(C式・ゼロ埋め・ステップ)
- 8) 途中失敗で即終了(明示チェックで止める)
- 9) 成否を集計して最後にまとめて終了判定
- 10) 並列実行(最大同時N、本体は関数)
- 11) findの結果を“安全に”forで回したい(NUL区切り→配列化)
- 12) 引数(positional params)を安全に回す
- 13) 除外を入れたグロブ(dot含める/特定名は除外)
- 14) 失敗しがちなパターンの正解例(クォート&–)
- “やってはいけない書き方”一覧(NG→理由→推奨)
- Bash道的な“お作法”ワンポイント
- まとめ
- 関連記事
即コピペ例(安全テンプレ)
# 1) リスト(語/配列/グロブ)
for x in item1 item2 item3; do
printf '%s\n' "$x"
done
# 2) 数値(動的・ステップ)
for ((i=1; i<=10; i++)); do
printf '%02d\n' "$i"
done
# 3) 小さな固定レンジ(brace)
for m in {01..12}; do
echo "$m"
done
Bash の for は3種類(最短の使い分け)
Bash の for は 「リスト形式」/「C式」/「brace展開」の3種類。
使い分けは “何を回すか” で決まります。まずはここから。
用途別・最短の正解
| 用途 | 最短の構文 | ポイント |
|---|---|---|
| 配列・単語・グロブ(ファイル)を順番に回す | for x in list; do ...; done | 要素は必ずダブルクォート(空白/改行安全) |
| 数値レンジ(動的・ステップ可) | for ((i=START; i<=END; i+=STEP)); do ...; done | 外部コマンド不要で高速・柔軟 |
| 小さな固定レンジ | for i in {01..12}; do ...; done | 静的展開。大きい範囲や変数は不向き |
Tips
- グロブ(例:
*.txt)は一致なし対策にshopt -s nullglobを併用すると安全。 - 大きい/可変レンジは brace ではなく C式 を選ぶとメモリ・速度面で有利。
for と while の使い分け(1分で判断)
原則
- for:既知の有限リストを順に処理(配列・引数・グロブ・小さな固定レンジ)。
- while:ストリーム/ファイルを順次読み取り、条件で回す処理。
迷ったら
- ファイル内容を 1行ずつ 安全に読む → while + read -r
findの結果を安全に流す(空白/NUL対策) →-print0+read -d ''か-exec … +
テンプレ:ファイルを1行ずつ安全に読む
# for ではなく while を使う(空白・バックスラッシュ・改行安全)
while IFS= read -r line; do
printf '%s\n' "$line"
done < file.txt
テンプレ:find の結果を安全に処理(NUL区切り)
# 1) while で受ける
find . -type f -name "*.jpg" -print0 |
while IFS= read -r -d '' path; do
echo "$path"
done
# 2) -exec を使う(大量ファイルに強い)
find . -type f -name "*.jpg" -exec mogrify -resize 1280x {} +
注意(変数のスコープ)
cmd | while ...; do ...; doneのように パイプ前段 を付けると、whileブロックはサブシェルになり、ブロック内で更新した変数を外側で使えないことがあります。
変数を外側に持ち出したいときは 入力リダイレクト(< file)で書くのが安全です。
実務に役立つサンプル集 14選
1) 配列を安全に回す(空要素・空白対応)
arr=('foo bar' '' 'baz')
for x in "${arr[@]}"; do
printf '[%s]\n' "$x"
done
2) 配列のインデックス+要素を同時に
arr=(a b c)
for i in "${!arr[@]}"; do
printf '%d:%s\n' "$i" "${arr[i]}"
done
3) 連想配列(Bash 4+)
declare -A conf=([env]=prod [port]=8080)
for k in "${!conf[@]}"; do
printf '%s=%s\n' "$k" "${conf[$k]}"
done
4) グロブでファイルを回す(未一致安全:nullglob)
shopt -s nullglob
for f in *.txt; do
[[ -e "$f" ]] || break
wc -l -- "$f"
done
shopt -u nullglob
5) ディレクトリだけ回す(状態汚染を避ける:サブシェル)
shopt -s nullglob
for d in */; do
( cd "$d" && do_something_in_here ) # ()でカレントを外に持ち出さない
done
shopt -u nullglob
6) 拡張子変換(jpg → webp)
shopt -s nullglob
for src in *.jpg; do
dst="${src%.jpg}.webp"
cwebp -quiet -- "$src" -o "$dst"
done
shopt -u nullglob
7) 数値レンジ(C式・ゼロ埋め・ステップ)
for ((i=1; i<=12; i+=1)); do
printf 'file-%02d.log\n' "$i"
done
8) 途中失敗で即終了(明示チェックで止める)
for f in *.csv; do
[[ -f "$f" ]] || continue
if ! process_csv -- "$f"; then
printf 'ERROR: %s\n' "$f" >&2
exit 1
fi
done
9) 成否を集計して最後にまとめて終了判定
failed=0
for it in "${tasks[@]}"; do
if ! run_task -- "$it"; then
printf 'FAIL: %s\n' "$it" >&2
((failed++))
fi
done
((failed==0)) || exit 1
10) 並列実行(最大同時N、本体は関数)
# 並列数
N=4
pids=()
worker() { # ここに本処理を書く
cmd -- "$1"
}
for x in "${items[@]}"; do
worker "$x" & pids+=("$!")
# 上限に達したら1つ終わるまで待つ
if (( ${#pids[@]} >= N )); then
wait -n
# 終了したPIDを配列から掃除(簡易)
tmp=(); for pid in "${pids[@]}"; do kill -0 "$pid" 2>/dev/null && tmp+=("$pid"); done
pids=("${tmp[@]}")
fi
done
wait # 残りを待つ
11) findの結果を“安全に”forで回したい(NUL区切り→配列化)
# 大量・空白OK。mapfileでNUL区切りを配列へ
mapfile -d '' -t files < <(find . -type f -name '*.jpg' -print0)
for f in "${files[@]}"; do
mogrify -resize 1280x -- "$f"
done
12) 引数(positional params)を安全に回す
# 使い方: script.sh file1 "file 2" ...
for arg in "$@"; do
printf 'ARG=%s\n' "$arg"
done
13) 除外を入れたグロブ(dot含める/特定名は除外)
shopt -s nullglob dotglob
GLOBIGNORE='.git:node_modules'
for p in *; do
printf '%s\n' "$p"
done
shopt -u nullglob; unset GLOBIGNORE
14) 失敗しがちなパターンの正解例(クォート&–)
pattern='*report*.txt'
shopt -s nullglob
for f in $pattern; do
grep -- 'ERROR' -- "$f" || true
done
shopt -u nullglob
“やってはいけない書き方”一覧(NG→理由→推奨)
| NG例 | 理由 | 推奨(安全な書き方) |
|---|---|---|
for f in $(ls) | 単語分割・グロブ再展開で壊れる(空白/改行に弱い) | shopt -s nullglobfor f in *; do printf '%s\n' "$f"; doneshopt -u nullglob |
for w in $(cat file.txt) | 空白/改行で分割され内容が破損 | (forではなく)while IFS= read -r line; do ...; done < file.txt |
for i in $(seq 1 100000) | seq は外部プロセスで遅い・壊れやすい連結 | for ((i=1; i<=100000; i++)); do ...; done |
for i in {1..$N} | brace展開は静的。変数を解釈しない | for ((i=1; i<=N; i++)); do ...; done |
for f in *.txt; do rm $f; done | 未一致で *.txt がそのまま渡る/未クォートで壊れる | shopt -s nullglobfor f in *.txt; do rm -- "$f"; doneshopt -u nullglob |
for f in "$DIR/*" | クォート位置が誤りでグロブ無効(文字列リテラル化) | for f in "$DIR"/*; do ...; done |
for f in $(find . -type f -name "*.jpg") | 空白/改行で分割。NUL安全でない | (forではなく)find . -type f -name "*.jpg" -print0 \| while IFS= read -r -d '' f; do ...; doneもしくは find ... -exec cmd {} + |
for x in ${arr[@]}; do cmd $x; done | 未クォートで空白/改行が壊れる | for x in "${arr[@]}"; do cmd -- "$x"; done |
for d in */; do cd "$d"; ...; done | ディレクトリ移動で状態が汚れる/戻し忘れ | for d in */; do ( cd "$d" \&\& ... ); doneまたは pushd/popd を使用 |
for f in *; do [ -f $f ] \&& grep PAT $f; done | 未クォート・オプション解釈の危険 | for f in *; do [[ -f "$f" ]] \&& grep -- "PAT" -- "$f"; done |
Bash道的な“お作法”ワンポイント
- 必ずクォート:
"$var"で単語分割やグロブ暴発を防ぐ - nullglob / dotglob:未一致→空、隠しファイルも対象
- IFS は変更しない:特に行処理では
read -r -d ''のように NUL区切りを使う set -u状態でのループ変数初期化忘れ注意
まとめ
Bashのfor文は、単純な繰り返しだけでなく、範囲指定・配列処理・ファイル操作・並列処理など、工夫次第で幅広く応用できます。
本記事で紹介したように、seqよりブレース展開やC風構文を活用し、nullglobやfind -print0で安全性を確保することで、現場でも安心して使えるスクリプトが書けます。
基本を押さえつつ、目的に合わせた構文を選び、クォートやエラー対策といった“Bash道”のお作法を取り入れることで、効率的かつ堅牢なシェルスクリプトを実現しましょう。
