導入
私の 資料.txt のような、スペースを含むファイル名。Windowsから持ち込まれたファイルには珍しくありません。クォートを忘れたスクリプトは、こういうファイルで静かに壊れます。
説明
変数を囲まないと、スペースの位置で別々の値に割れます。
#!/bin/bash
file="my report.txt"
echo "内容" > "$file"
echo "=== 囲んだ場合 ==="
cat "$file"
echo "=== 囲まなかった場合 ==="
cat $file
囲まなかったほうは my と report.txt という2つのファイルを探しに行き、両方とも見つからずエラーになります。
flowchart TD
A["file=\"my report.txt\""] --> B["cat $file(囲まない)"]
A --> C["cat \"$file\"(囲む)"]
B --> D["cat my report.txt<br/>→ 2つのファイルを探す → エラー"]
C --> E["cat 'my report.txt'<br/>→ 正しく1つのファイル"]
覚えるルールは1つだけです。変数を使うときは、必ず " で囲む。
# よくない
rm $file
cp $src $dst
for f in $(ls); do
# よい
rm "$file"
cp "$src" "$dst"
for f in *; do
最後の for f in $(ls) → for f in * も重要です。ls の出力を使うと空白で割れますが、ファイル名展開(*)ならファイル名が壊れません。
例外はほぼありません。「囲むと動かない」場面(* を展開させたいときなど)以外は、機械的に囲んでしまって構いません。迷ったら囲む。それだけで、この種のバグはほぼ消えます。
$@を使うときも同じで、"$@"と囲むのが正解です。囲まないと、スペースを含む引数が分割されてしまいます。
やってみよう
スクリプトを実行して、囲んだほうだけが成功することを確かめましょう。次にファイル名を file="report.txt"(スペースなし)に変えて実行すると、どちらも成功してしまいます。だからこそ、テストでは気づけず本番で壊れるのです。
演習
スペースを含むファイル名でも正しく読めるように、cat の引数を二重引用符で囲んで 内容 を表示してください。
ヒント1を見る
cat $file を cat "$file" に直します。
ヒント2を見る
変数を使う場所はすべて "$file" の形にします。