シェルは実行する前に書き換えている。引用符を付ける理由を図にした

打った文字列はそのまま渡りません。決まった順番で展開されてから実行されます。変数を引用符で囲む理由は、この順番の3番目にあります。

この記事の結論

  1. 打った文字列はそのまま渡らない。順番に展開されてから実行される
  2. 語の数が増えるのは3つの展開だけ
  3. 変数を引用符で囲む理由は、そのうちの1つを止めるため
  • 打った文字列はそのまま渡らない。順番に展開されてから実行される
  • 語の数が増えるのは3つの展開だけ
  • 変数を引用符で囲む理由は、そのうちの1つを止めるため

変数を引用符で囲むのは「お作法」だと思っていた。展開の順番を知ったら、囲まないと壊れる理由がはっきりした。

打った文字列が波括弧・変数・語の分割・ファイル名・引用符除去の順に展開される流れと、引用符の有無で結果が変わる例を並べた図
図1:順番が決まっている。事故が起きるのは、ほぼ3番目。

順番は決まっている

手引きには、この順で行うと書かれている。

  1. 波括弧の展開
  2. チルダの展開、変数の展開、計算の展開、コマンド置換(左から右へ)
  3. 語の分割
  4. ファイル名の展開
  5. 引用符の除去

注目すべきは2と3の順番になる。変数が値に変わったあとで、語の分割が行われる。

ここが分かると、原因が見える

変数の中身に空白が入っていた場合、値になった時点では1つでも、次の段階で別々の語に割れる。

手引きはこう書いている。

シェルは、二重引用符の中で起きなかった変数の展開、コマンド置換、計算の展開の結果を走査して、語の分割を行う。

区切りに使われる文字は IFS で決まり、既定では空白・タブ・改行になる。

SPONSORED

語の数が増えるのは3つだけ

手引きの、もう一つ重要な一文がこれになる。

波括弧の展開、語の分割、ファイル名の展開だけが展開結果の語の数を増やせる。その他の展開は、1つの語を1つの語に展開する。

つまり事故の原因は、この3つのどれかに絞られる。変数を使って意図しない動きになったときは、まずここを疑えばいい。

実際に起きること

ファイル名に空白が入っているときが、いちばん分かりやすい。

書き方渡るもの結果
rm $f2つの語(空白で割れる)別々の名前として扱われ、見つからない
rm "$f"1つの語のまま意図どおりに動く

出るのは「ファイルがありません」というメッセージになる。名前は合っているのに見つからないので、原因を探す方向がずれやすい。

囲むと何が止まるか

二重引用符で囲むと、語の分割とファイル名の展開が行われない。変数の展開そのものは行われるので、値は入る。

止めたいのは3と4であって、2ではない。必要な展開は残したまま、増える展開だけを止めるのが二重引用符の役割になる。

一重と二重の違い

一重引用符で囲むと、変数の展開も止まる。中身がそのまま文字列として渡る。

だから使い分けは単純になる。値を入れたいなら二重、入れたくないなら一重。迷ったら二重から始めればいい。

コマンド置換のときも同じ

別のコマンドの出力を受け取るときにも、同じことが起きる。

出力に空白や改行が含まれていれば、その位置で語に割れる。ファイルの一覧を受け取る処理で、名前に空白が入った瞬間に崩れるのは、これが理由になる。

展開の順番でいえば、コマンド置換は2番目、語の分割は3番目になる。出力が入ったあとに割られるので、出力側をどれだけ整えても防げない。

行ごとに扱いたいとき

一覧を1件ずつ処理したいなら、語の分割に任せない。区切りを自分で決めて、1行ずつ読む形にする。

空白で割れてほしいのか、改行で割れてほしいのかは、処理ごとに違う。既定の区切りは空白・タブ・改行の3つなので、何もしなければ空白でも割れる。ここが食い違いの正体になる。

決めている書き方

手で打つときはともかく、手順として残す文章では次のようにしている。

  • 変数を書くときは、必ず二重引用符で囲む
  • 囲まないのは、意図して語に割りたいときだけ。そのときは理由を書き添える
  • ファイル名を扱う処理は、空白入りの名前で一度試す

3番目が効く。空白の入ったファイルを1つ作って通してみるだけで、この種の不具合は事前に出る。

結果を次の処理に渡すときの注意はgrepは行ごとに見ているだけにも書いた。終わったときの番号と、語の分割が、手順を書くときの二大つまずきになる。

まとめ

  • 展開の順番は波括弧 → 変数など → 語の分割 → ファイル名 → 引用符の除去
  • 変数が値になったあとで、語の分割が起きる
  • 区切りは IFS で決まり、既定は空白・タブ・改行
  • 語の数を増やせるのは3つの展開だけ。事故はここに絞られる
  • 二重引用符は語の分割とファイル名の展開だけを止める
  • 一重引用符は変数の展開も止める
SPONSORED

出典

図は本記事のために作成したもの。

新しい記事が出たら、ブラウザの通知でお知らせします。

メールアドレスの登録は要りません。いつでも止められます。

AUTHOR

北海道のWEB屋

北海道を拠点に、Web業界14年・サポート実績1,000件以上。WordPressの復旧、サーバー移転、独自ドメインとメールの設定、PHP・JavaScriptの修正まで対応します。相談と見積もりは無料です。

SPONSORED

関連記事

RELATED