この記事の結論
zshのforで変数が分割されないのは、不具合ではなくzshの既定の仕様です。AIが書いた選別削除のコマンドで、6件残すはずの下書き41件が全部消えた当社の実録と、配列での直し方、削除の前に対象を一覧で出す手順をまとめました。
目次6項目
【実践講座】Claude Code 2daysセミナー(第二の脳構築講座)の詳細を見る →
zshのforで変数が分割されないのは、不具合ではなくzshの既定の仕様です。空白で区切ったリストは1語として扱われます。直し方は、リストを配列で持つことです。当社は2026年9月、AIが書いた選別削除のコマンドでこれを踏み、6件残すはずの下書き41件を全部消しました。
zshのforで変数が分割されないのは、zshの既定の仕様
zshとは、Macのターミナルで文字のコマンドを受け取り、実行する役目のプログラム(シェル)の一種です。Appleのサポートページによると、macOS 10.15以降のMacは、zshを標準のシェルとして使います。それより前の標準はbashでした。
問題になるのは、次の形のコマンドです。
LIST="a b c"
for x in $LIST; do echo "[$x]"; done
bashでは [a] [b] [c] の3行が出ます。zshでは [a b c] の1行だけが出ます。エラーは出ません。警告も出ません。ループが3回ではなく1回だけ回り、そのまま終わります。
単語分割とは、変数の中身を空白の位置で切り、複数の語として扱うシェルの動作のことです。zshの公式FAQは、多くのシェルが行うこの分割をzshは既定で行わず、変数はそのまま保たれると説明しています。あわせて、これはバグではないと明記しています。
書き方ごとの違いを表にします。出力は、当社のMacで2026年10月12日に実行して確かめたものです。
| 書き方 | zshでの回数 | bashでの回数 |
|---|---|---|
LIST="a b c" を for x in $LIST で回す | 1回(a b c が1語) | 3回 |
LIST=(a b c) を for x in $LIST で回す | 3回 | (書き方が異なるため対象外) |
LIST="a b c" を for x in ${=LIST} で回す | 3回 | (zsh専用の書き方) |
setopt shwordsplit の後に for x in $LIST で回す | 3回 | (zsh専用の設定) |
怖いのは、表示するだけのコマンドではありません。「このリストに載っているものは残し、それ以外は消す」という選別に使ったときです。リストが1語になると、どのファイル名とも一致しません。すると、全部が「残すリストに無いもの」と判定されます。
zshで変数を分割して回す3つの直し方
zshの公式FAQとマニュアルに載っている直し方は3つです。おすすめの順に並べます。
- リストを配列で持つ。 配列とは、複数の値を1つずつ区切ったまま、1つの名前で持てる変数の形のことです。
LIST=(a b c)と丸かっこで書きます。zshの公式FAQは、zshで語を分けたいときの自然な方法は配列だと案内しています。 - その変数だけを分割する。
${=LIST}と書くと、その場所でだけ分割されます。公式マニュアルは、この書き方が区切りにIFSという設定の値を使うと説明しています。 - 設定で分割を有効にする。
setopt shwordsplitを実行すると、bashなどと同じ動作になります。公式マニュアルではSH_WORD_SPLITというオプションです。
僕は1番をすすめます。配列なら、空白を含むファイル名も1つの値として持てます。2番と3番は、空白で切る動作に戻すだけなので、名前に空白のあるファイルが2つに割れます。
あなたのターミナルで安全に確かめるなら、次の2行を貼ってください。画面に文字を出すだけで、ファイルには触れません。
LIST="a b c"; for x in $LIST; do echo "[$x]"; done
LIST=(a b c); for x in $LIST; do echo "[$x]"; done
1行目が [a b c] の1行、2行目が3行なら、そのターミナルはzshの既定の動作です。いま動いているのがzshかどうかは、echo $ZSH_VERSION でも分かります。版の数字が出ればzshです。
選別の形でも確かめられます。次の例も表示だけです。
KEEP=(memo_01 memo_02)
for f in memo_01 memo_02 memo_03; do
hit=no
for k in $KEEP; do
if [ "$f" = "$k" ]; then hit=yes; fi
done
echo "$f 残す=$hit"
done
当社のMacのzshでは、memo_01 と memo_02 が 残す=yes、memo_03 が 残す=no と出ました。1行目を KEEP="memo_01 memo_02" に変えると、3つとも 残す=no になります。これが、次の章の事故と同じ形です。
裏方で実際に起きた例:2026年9月、6件残すはずの下書き41件が全部消えた
ここからは、僕の会社(株式会社UrakaTA)の失敗の記録からの転記です。
2026年9月5日、社内で使っているツールに、テスト用の下書きが41件たまっていました。このうち6件だけを残し、残りを消す作業を、Claude Codeで進めていました。Claude Codeとは、ターミナルやデスクトップアプリから、AIのClaudeにファイルの操作やコマンドの実行まで任せられるツールです。
組み立てられたコマンドは、残す6件の名前を、空白で区切った1つの文字列として持つ形でした。その文字列をforで回し、名前が一致したものを「残す」と判定する作りです。bashなら、意図どおり6回まわります。
実際に動いたのはzshでした。6件分の名前は1語として扱われ、41件のどれとも一致しません。全件が「残す対象ではない」と判定され、41件すべてが削除されました。エラーは1つも出ていません。コマンドは正常に終わっています。
消えた中には、社内の資料が例として参照していた下書きも含まれていました。どれも再生成できるテスト用のデータだったので、作り直して、資料の参照を貼り直しました。復旧にかかった時間は記録に残していないので、ここには書きません。
原因は2つありました。1つは、bashの書き方をそのままzshで使ったことです。もう1つは、削除の前に「何が消えるのか」を一覧で出していなかったことです。一覧を出していれば、6件が残る側に入っていないことに、その場で気づけました。
AIに選別削除を頼むときの再発防止:配列と事前の一覧
この件のあと、当社の記録に残した決まりは4つです。
- 残すリストは配列で書く。 文字列で持つなら
${=変数}の形で分割する。 - 削除の前に、対象の一覧を表示する。 dry-runとは、実際の削除や移動を行わず、対象だけを表示して確かめる試し実行のことです。これを必ず挟みます。
- 再生成できるデータでも、確認を省かない。 ほかの資料やテストが参照しているものは、消す前に確かめます。
- Claude CodeのBashは、zshで動く前提で書く。
選別削除の一覧は、次の形で出せます。消す予定のものを表示するだけで、まだ何も消しません。memo_* の部分は、あなたのフォルダにある対象の名前の形に置き換えてください。一致するファイルが無いと、zshは no matches found と出して止まります。
KEEP=(memo_01 memo_02)
for f in memo_*; do
hit=no
for k in $KEEP; do
if [ "$f" = "$k" ]; then hit=yes; fi
done
if [ "$hit" = no ]; then echo "消す予定: $f"; fi
done
表示された件数と名前が、あなたの想定と合っているかを見ます。合っていたときだけ、echo "消す予定: $f" の部分を削除のコマンドに置き換えて実行します。件数が「全部」になっていたら、そこで止めてください。
コマンドを自分で読めなくても、AIへの頼み方で同じことができます。依頼文に、次の2行を添えます。
このMacのシェルはzshです。リストは配列で書いてください。
削除や移動の前に、対象と残すものを件数つきの一覧で見せて、私の返事を待ってください。
毎回書くのが手間なら、Claude Codeが作業のたびに読む指示書(CLAUDE.md)に同じ内容を書いておきます。当社も、失敗の記録に「削除系は先に対象一覧を出す」と残し、次の作業から参照させています。
Claude Codeのほかの困りごとは、Claude Codeのエラー・困りごと一覧から症状で引けます。
業務でAIにファイルの一括処理を任せるときの注意
Claude Codeがどのシェルで動くかは、環境で確かめます。 公式ドキュメントは、Claude Codeがセッションの最初に、使っているシェルに応じて ~/.zshrc、~/.bashrc、~/.profile のいずれかを読み込むと説明しています。「Bashという名前のツールだからbashで動く」とは書かれていません。当社のMacでは、zshで動いていました。あなたの環境では、AIに echo $ZSH_VERSION を実行させて確かめてください。
エラーが出ないことは、正しく動いた証拠になりません。 今回のコマンドは正常に終わっています。AIの「完了しました」という報告も、うそではありません。判定の条件は、報告ではなく、消す前の一覧と消した後の件数に置きます。
削除の許可は、一覧を見てから出します。 Claude Codeは、コマンドの実行前に許可を求める設定にできます。許可の画面が何度も出て困る場合の整え方は、Claude Codeで許可の確認が何度も出るときの対処に書きました。削除を含むコマンドを、確認なしで通る設定にしないことをおすすめします。
戻せる場所で作業します。 ターミナルの削除は、ごみ箱を通りません。当社の件は再生成できるデータでしたが、写真や契約書のように作り直せないものなら、復旧できませんでした。作り直せないファイルを選別するときは、先にフォルダごとコピーを取ってから始めてください。
チームで使うなら、決まりを指示書に書いて共有します。 人によって頼み方が違うと、確認を挟む人と挟まない人が出ます。「削除の前に一覧」を各自の気づかいに任せず、共通の指示書に書いておきます。AIに任せた作業でのほかの失敗は、AI導入の失敗事例にまとめています。
当社が確かめたのは、自社のMacでの1件です。 2026年10月時点の公式ドキュメントと、当社の記録にもとづいて書いています。別の環境や設定では動きが違う可能性があります。
AIにファイルの整理やコマンドの実行を任せるときの、指示書の書き方、確認の挟み方、任せてよい範囲の決め方を自社で回せる形にしたい方には、下のセミナーで具体的な作り方をお伝えしています。
よくある質問
zshのforで変数が分割されないのは不具合ですか?
不具合ではありません。zshの公式FAQは、空白を含む変数をクオートなしで書いても分割しないのが既定の動作で、バグではないと明記しています。分けたいときは、リストを配列で持つか、その変数だけを分割する書き方を使います。
bashと同じように分割させる設定はありますか?
あります。SH_WORD_SPLITというオプションを有効にすると、bashなどと同じように分割されます。ただしzshの公式FAQは、分割したいときの自然な方法は配列だと案内しています。設定に頼るより、リストを配列で書くほうが、環境が変わっても同じ結果になります。
AIにファイルの一括削除を頼むとき、何を伝えればよいですか?
「zshで動く前提で、リストは配列で書く」「削除の前に、消す対象と残す対象を一覧で見せる」の2つを依頼文に添えます。当社は2026年9月に、この確認を挟まなかったために、6件残すはずの下書き41件を全部消しました。