定義
単語分割(word splitting)とは、シェル(bash や zsh のようなコマンド解釈器)が変数やコマンド置換を展開したあと、その結果文字列を空白・タブ・改行を基準に複数の引数(argument)へ分割する動作である。**クォート(quoting)**は値を二重引用符 "..." で囲んでこの分割を防ぎ、値ひとつを引数ひとつとして渡すようにするものだ。
ひとことで言えば、変数に入っている値が正しくても、クォートしなければシェルはその値を複数の断片に分けてコマンドへ渡してしまう。
なぜ必要か
パスに空白が含まれる状況を考えてみよう。DIR="/tmp/My Files" を ls $DIR と書くと、シェルは $DIR を展開したあと空白で分割し、ls /tmp/My Files として渡す。ls は存在しない2つのパスを受け取り、「そのようなファイルはない」と失敗する。値(echo "$DIR")は明らかに正しいのにコマンドだけが失敗する、初学者を長く悩ませる症状だ。
フック(hook)スクリプトや CI 設定のように、人が頻繁に見ない場所では、このミスは静かに壊れる。ホームディレクトリのパス、プラグインのルート、ユーザーが作成したファイル名など、空白や特殊文字が混じりうる値は実際に珍しくないため、この規則を知らないと繰り返し引っかかる。
仕組み
シェルが一行を実行するまでには、おおよそ次の順序をたどる。
- 展開(expansion):
$VAR、${VAR}、$(cmd)などを実際の値に置換する。 - 単語分割(word splitting): 展開結果を
IFS(Internal Field Separator、デフォルト値は空白・タブ・改行)の文字を基準に複数のトークンへ分ける。この段階はクォートされていない展開にのみ適用される。 - パス名展開(globbing):
*、?のようなパターンをファイル名へ展開する(これもクォートされていない場合のみ)。 - 最終的なトークンをコマンドの引数として渡す。
"$VAR" で囲むと2・3の段階がまるごと省略され、値が引数ひとつとして保たれる。単一引用符 '...' は1の段階(展開)そのものを止め、リテラルとして扱う。
実務での適用
静的検査器 shellcheck はクォート漏れ(SC2086 など)を自動で検出してくれる。スクリプトを CI に入れる前に一度回すのが、もっとも安価な防御だ。
トレードオフ
- クォートのコストはほぼゼロ。「常にクォート」をデフォルトにしても、失うものは実質的にない — 例外は意図的に分割が必要な少数の場合だけだ。
- 意図的な分割が必要なとき(例: 空白で区切られたオプション一覧を引数として展開したいとき)は、クォートを外すか配列を使う。ただしこの場合も配列のほうが安全で明確だ。
使うべきでない場合
- 値に空白や特殊文字が絶対にないと確信できないなら、クォートを外してはならない。「今は大丈夫」が、将来のファイル名やパスで壊れる。
- クォートを外して単語分割に依存する「複数引数への展開」は、配列があるシェルではアンチパターンだ —
"${arr[@]}"のほうが安全である。
よくある間違い
$VARをクォートなしでコマンドへ渡す。 値は正しいのにコマンドが失敗したら、まず疑うべき箇所。- 空値の扱い漏れ:
cmd $EMPTYは引数0個になり、後続の引数が前へずれる。"$EMPTY"は空文字列1個を保つ。 - コマンド置換のクォート漏れ:
x=$(cmd)は改行が空白へ畳まれて分割される。"$(cmd)"で囲む。 - 単一引用符の中で変数展開を期待する:
'$VAR'はリテラルの$VARだ。値の埋め込みは二重引用符で。
関連概念
- serverless-stateless-execution — 実行環境ごとにパスや状態が変わる文脈では、クォートのミスが表面化しやすい