Bashのfor文は3種類|最適な書き方と安全テンプレ14選

分岐・反復
スポンサーリンク

Bashのfor文は、リストや範囲の反復処理に欠かせない基本中の基本。

しかし、よくある罠やちょっとした安全策を知らないと、思わぬバグやパフォーマンス低下を招く可能性があります。

本稿では、「安全・高速・読みやすさ」の3点にこだわって、Bash道流にガチ整理しました。

for 以外にもBashに関するテクニックに関しては 「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 nullglob
for f in *; do printf '%s\n' "$f"; done
shopt -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 nullglob
for f in *.txt; do rm -- "$f"; done
shopt -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風構文を活用し、nullglobfind -print0で安全性を確保することで、現場でも安心して使えるスクリプトが書けます。

基本を押さえつつ、目的に合わせた構文を選び、クォートやエラー対策といった“Bash道”のお作法を取り入れることで、効率的かつ堅牢なシェルスクリプトを実現しましょう。

スポンサーリンク
Bash玄

はじめまして!Bash玄です。

エンジニアとしてシステム運用に携わる中で、手作業の多さに限界を感じ、Bashスクリプトを活用して業務を効率化したのがきっかけで、この道に入りました。「手作業は負け」「スクリプトはシンプルに」をモットーに、誰でも実践できるBashスクリプトの書き方を発信しています。

このサイトでは、Bashの基礎から実践的なスクリプト作成まで、初心者でもわかりやすく解説しています。少しでも「Bashって便利だな」と思ってもらえたら嬉しいです!

# 好きなこと
- シンプルなコードを書くこと
- コマンドラインを快適にカスタマイズすること
- 自動化で時間を生み出すこと

# このサイトを読んでほしい人
- Bashに興味があるけど、何から始めればいいかわからない人
- 定型業務を自動化したい人
- 効率よくターミナルを使いこなしたい人

Bashの世界に一歩踏み出して、一緒に「Bash道」を極めていきましょう!

Bash玄をフォローする