はじめに
2日がかりで、Mac で動かしている自動ジョブの棚卸しをしていた。ログや設定をまとめて読むために、コマンドを ; でつなぎ、ジョブごとに区切りの行を出していた。
echo ===step7; tail -n 20 step7.log; echo ===step9; tail -n 20 step9.log
返ってきたのは1行だけだった。
(eval):1: ==step7 not found
tail の出力も、2つ目の区切りも出ない。終了コードは1。区切りのつもりで書いた echo が、1つ目で全体を止めていた。
同じ形を、この2日間で2回踏んでいる。1回目で気づいたのに、対策を書き残さなかった。その後も、別の作業の最初の調査コマンドで3回目を踏んだ。
= で始まる語は、コマンドのパスになる
zsh には EQUALS というオプションがあり、既定で有効になっている。語の先頭が = だと、その後ろをコマンド名として探し、見つかったパスに置き換える。
zsh 5.9(macOS 標準)で、設定ファイルを読まずに試した。
zsh -f -c 'echo =ls'
# /bin/ls
=ls は /bin/ls になる。which ls を埋め込んだのと同じ働きをする。
= が2つ以上続くと、先頭の1つを外した残りがコマンド名として探される。
zsh -f -c 'echo ==ls'
# zsh:1: =ls not found
zsh -f -c 'echo ===step7'
# zsh:1: ==step7 not found
zsh -f -c 'echo ====='
# zsh:1: ==== not found
===== は ==== というコマンドを探しに行き、そんなコマンドは無いので展開が失敗する。
後ろのコマンドまで止まる
展開の失敗は、そのコマンドだけでは終わらなかった。
| 書き方 | 出たもの | 終了コード |
|---|---|---|
echo ===step7; echo after |
==step7 not found だけ |
1 |
echo ===step7 && echo after |
同上 | 1 |
echo ===x || true; echo after |
同上 | 1 |
改行で区切る(echo before / echo ===step7 / echo after) |
before のあと止まる |
1 |
| スクリプトファイルに書く | 同上 | 1 |
関数の中(f(){ echo ===x; echo in-f; }; f; echo after-f) |
どれも出ない | 1 |
サブシェル(( echo ===x ); echo after) |
after が出る |
0 |
; で区切っても、改行で区切っても、そこで止まる。|| true を付けても続かない。展開の失敗は「コマンドが失敗した」ではなく、コマンドを組み立てる前の段で起きているので、|| が効く場所まで届かない。
続いたのは、( ) でサブシェルに閉じ込めたときだけだった。サブシェルの中で止まり、外側は次の行へ進む。
同じ1行を bash 3.2(macOS 標準の /bin/bash)で流すと、そのまま表示される。
bash -c 'echo ===step7; echo after'
# ===step7
# after
bash には = の展開が無い。bash で書いていた区切りを zsh に持ち込むと、ここで止まる。
止まらない書き方
区切りの行は、引用符で囲めば展開されない。
zsh -f -c "echo '====='; echo after"
# =====
# after
= 以外で始めてもいい。echo --- も echo '[step7]' も、そのまま出た。
オプションを切る方法もある。
zsh -f -c 'setopt noequals; echo ===step7; echo after'
# ===step7
# after
ただ、noequals はそのシェルの中だけで効く。毎回のコマンドの頭に足すより、= で始まる語を引用符なしで書かないほうが手間が少なかった。
= が語の途中にある場合は展開されない。echo a=b は a=b と出る。= 1文字だけの echo = も、そのまま = が出た。危ないのは、語の先頭が = で、後ろに何か続く形だけだ。
AI にシェルを打たせるとき
この3回は、どれも Claude Code にコマンドを組ませていたときに出た。Claude Code の Bash ツールは、Mac では利用者のログインシェル(ここでは zsh)で動く。区切りに ===== を使う書き方は bash でも Python の出力でもよく見る形で、手癖として出てきやすい。
1回の呼び出しに調べものを ; で詰めていると、最初の区切りで全部が止まり、返ってくるのは not found の1行になる。何も調べられていないのに、エラーの文面からは区切りの行が原因だと分かりにくい。
いまは作業のメモに「区切りは最初から echo '-----' と引用符つきで書く」と残している。
区切りの線を引いたつもりだった。zsh には、それが ==== という名前のコマンドを探せ、という指示に見えていた。