以下 sh/bash/zsh について同様ですが、例では bash とします。
変数から変数への代入
シェルスクリプトで変数から変数へ代入する場合、ダブルコーテーション("
)で括る必要はありません。
# クォートあり
hoge="$fuga"
# クォートなし
hoge=$fuga
上記はどちらも同じ結果になります。
src=$'abc def\nghi'
echo "=== src ==="
echo "$src"
# 代入
dest=$src
echo "=== dest ==="
echo "$dest"
=== src ===
abc def
ghi
=== dest ===
abc def
ghi
コマンド置換の結果を代入
コマンド置換でも同様です。
バッククォート(`
) の場合
src=$'abc def\nghi'
echo "=== src ==="
echo "$src"
# 代入
dest=`echo "$src"`
echo "=== dest ==="
echo "$dest"
=== src ===
abc def
ghi
=== dest ===
abc def
ghi
$()
の場合
src=$'abc def\nghi'
echo "=== src ==="
echo "$src"
# 代入
dest=$(echo "$src")
echo "=== dest ==="
echo "$dest"
=== src ===
abc def
ghi
=== dest ===
abc def
ghi
どちらもクォートは不要です。
仮にクォートしたとしても結果は同じですが、スマートに書く方法を知っておくのも良いのではないでしょうか。
続いて、zsh 特有の問題についても触れておきます。
(zsh でシェルスクリプトを書くかもしれない方は要注意です)
zsh で代入時に2回以上変数展開を行う場合
zsh では以下のように変数展開を一度に複数回行うことができます。
(bash では複数回の変数展開は行えません。2回に分けて行う必要があります)
## ここ重要
setopt SH_WORD_SPLIT
## パスっぽいもの
src='/aaa aa/bbb bb/ccc cc/ddd dd'
echo "=== src ==="
echo "$src"
## 1つ上のディレクトリを取得 (クォート不要)
dest1=${src%/*}
echo "=== dest1 ==="
echo "$dest1"
## 2つ上のディレクトリを取得 (クォートあり)
dest2_quote="${${src%/*}%/*}"
echo "=== dest2_quote (クォートあり) ==="
echo "$dest2_quote"
## 2つ上のディレクトリを取得 (クォートなし)
dest2_no_quote=${${src%/*}%/*}
echo "=== dest2_no_quote (クォートなし) ==="
echo "$dest2_no_quote"
=== src ===
/aaa aa/bbb bb/ccc cc/ddd dd
=== dest1 ===
/aaa aa/bbb bb/ccc cc
=== dest2_quote (クォートあり) ===
/aaa aa/bbb bb
=== dest2_no_quote (クォートなし) ===
aa bb cc
例にある「2つ上のディレクトリを取得」を行ったときのように、2回以上変数展開を行う場合はダブルコーテーションでクォートする必要があります。
ただし、setopt SH_WORD_SPLIT
を設定していなかった場合には単語分割が発生しないので関係ありません。
SH_WORD_SPLIT
はデフォルトではオフなので、独立したシェルスクリプトでは SH_WORD_SPLIT
を自分で制御できるので問題になることは少ないですが、インタラクティブシェルに source
して使うようなシェルスクリプトの場合は、ユーザが setopt SH_WORD_SPLIT
を設定している可能性があるため、常に単語分割を意識する必要があります。
(補完関数を書く場合も同様ですね)
zsh で変数宣言(local/typeset)と同時に代入する場合
この場合も setopt SH_WORD_SPLIT
を設定している場合のみ問題となります。
(bashではクォートがなくても大丈夫みたいです)
func() {
setopt SH_WORD_SPLIT
src=$'abc def\nghi'
echo "=== src ==="
echo "$src"
## クォートあり
local dest_quote="$src"
echo "=== dest_quote (クォートあり) ==="
echo "$dest_quote"
## クォートなし
local dest_no_quote=$src
echo "=== dest_no_quote (クォートなし) ==="
echo "$dest_no_quote"
}
func
=== src ===
abc def
ghi
=== dest_quote (クォートあり) ===
abc def
ghi
=== dest_no_quote (クォートなし) ===
abc
このように、クォートしないと期待した動作になりません。
また、コマンド置換の場合は、setopt SH_WORD_SPLIT
を 設定していない 場合でも正常に代入できません。
func() {
## デフォルトなのでなくても同じ
unsetopt SH_WORD_SPLIT
src=$'abc def\nghi'
echo "=== src ==="
echo "$src"
## クォートあり
local dest_quote="$(echo "$src")"
echo "=== dest_quote (クォートあり) ==="
echo "$dest_quote"
## クォートなし
local dest_no_quote=$(echo "$src")
echo "=== dest_no_quote (クォートなし) ==="
echo "$dest_no_quote"
}
func
=== src ===
abc def
ghi
=== dest_quote (クォートあり) ===
abc def
ghi
=== dest_no_quote (クォートなし) ===
abc
まとめ
- 変数から変数へ代入にはクォート不要
- コマンド置換(
`...`
,$(...)
)の結果を変数へ代入する場合もクォート不要 - zsh で
source
するタイプのスクリプトを書く場合はSH_WORD_SPLIT
が設定されている可能性を考えること - zsh で複数回変数展開する場合はクォートが必要 (
SH_WORD_SPLIT
あり) - zsh で変数宣言と同時に代入する場合はクォートが必要 (
SH_WORD_SPLIT
あり) - zsh で変数宣言と同時にコマンド置換する場合はクォートが必要 (
SH_WORD_SPLIT
関係なし)
zsh は便利だけどシェルスクリプトを書く場合はハマりポイントも多いので注意しよう。
zsh に限らず、単語分割の問題はスペースや改行が含まれない場合には問題にならないので非常に気付きづらいです。
シェルスクリプトを書く時は常に単語分割を意識するようにしましょう。
あと、ちゃんとテストしましょう。