什麼時候需要雙引號?
過去的舊建議是對涉及 a 的任何表達式進行雙引號
$VARIABLE
,至少如果有人希望它被 shell 解釋為一個單獨的項目,否則,內容中的任何空格$VARIABLE
都會拋出 shell。但是,我了解,在最新版本的 shell 中,不再需要雙引號(至少出於上述目的)。例如,在
bash
:% FOO='bar baz' % [ $FOO = 'bar baz' ] && echo OK bash: [: too many arguments % [[ $FOO = 'bar baz' ]] && echo OK OK % touch 'bar baz' % ls $FOO ls: cannot access bar: No such file or directory ls: cannot access baz: No such file or directory
zsh
另一方面,相同的三個命令成功。因此,根據這個實驗,似乎可以在bash
中省略雙引號[[ ... ]]
,但不能[ ... ]
在命令行參數中或在命令行參數中省略,而zsh
在所有這些情況下都可以省略雙引號。但是從上述軼事範例中推斷出一般規則是一個機會命題。很高興看到何時需要雙引號的摘要。我主要對
zsh
、bash
和感興趣/bin/sh
。
首先,將 zsh 與其他部分分開。這不是舊 shell 與現代 shell 的問題:zsh 的行為不同。zsh 設計者決定使其與傳統的 shell(Bourne、ksh、bash)不兼容,但更易於使用。
其次,始終使用雙引號比記住何時需要它們要容易得多。大多數時候都需要它們,因此您需要在不需要它們時學習,而不是在需要它們時學習。
簡而言之,在需要單詞列表或模式的任何地方都需要雙引號。在解析器需要原始字元串的上下文中,它們是可選的。
沒有引號會發生什麼
請注意,如果沒有雙引號,會發生兩件事。
- 首先,擴展的結果(參數替換的變數值,如
${foo}
,或命令替換的命令輸出,如$(foo)
)被拆分為包含空格的單詞。更準確地說,擴展的結果在
IFS
變數值中出現的每個字元處拆分(分隔符)。如果分隔符序列包含空格(空格、製表符或換行符),則將空格計為單個字元;前導、尾隨或重複的非空白分隔符會導致空欄位。例如, withIFS=" :"
,:one::two : three: :four
產生空欄位 beforeone
, betweenone
andtwo
, and (一個單一的) betweenthree
andfour
。 2. 如果拆分產生的每個欄位包含一個字元,則將其解釋為一個 glob(萬用字元模式)\[*?
。如果該模式匹配一個或多個文件名,則該模式將替換為匹配的文件名列表。不帶引號的變數擴展
$foo
通俗地稱為“split+glob 運算符”,與之相反,它只"$foo"
取變數的值foo
。命令替換也是如此:"$(foo)"
是命令替換,$(foo)
是命令替換後跟 split+glob。哪裡可以省略雙引號
以下是我在 Bourne 風格的 shell 中能想到的所有情況,您可以在其中編寫不帶雙引號的變數或命令替換,並且該值按字面意思解釋。
- 在作業的右側。
var=$stuff a_single_star=*
請注意,您確實需要在 之後使用雙引號
export
,因為它是一個普通的內置函式,而不是關鍵字。這僅適用於某些 shell,例如 dash、zsh(在 sh 仿真中)、yash 或 posh;bash 和 ksh 都特別對待export
。export VAR="$stuff"
- 在一份
case
聲明中。case $var in …
請注意,您確實需要在案例模式中使用雙引號。分詞不會發生在大小寫模式中,但未引用的變數被解釋為模式,而引用的變數被解釋為文字字元串。
a_star='a*' case $var in "$a_star") echo "'$var' is the two characters a, *";; $a_star) echo "'$var' begins with a";; esac
- 雙括號內。雙括號是 shell 的特殊語法。
[[ -e $filename ]]
=
除了在需要模式或正則表達式的地方確實需要雙引號:在or==
或!=
or的右側=~
。a_star='a*' if [[ $var == "$a_star" ]]; then echo "'$var' is the two characters a, *" elif [[ $var == $a_star ]]; then echo "'$var' begins with a" fi
您確實需要像往常一樣在單括號內使用雙引號,
[ … ]
因為它們是普通的 shell 語法(它是一個恰好被呼叫的命令[
)。請參見單括號或雙括號
- 在非互動式 POSIX shell(not
bash
, norksh88
)中的重定向。echo "hello world" >$filename
某些 shell 在互動時確實將變數的值視為萬用字元模式。POSIX 禁止在非互動式 shell 中進行這種行為,但一些 shell 包括 bash(POSIX 模式除外)和 ksh88(包括當發現為(假設)
sh
像 Solaris 等一些商業 Unices 的 POSIX 時)仍然在那裡執行(bash
也嘗試拆分並且重定向失敗,除非split+globbing只產生一個單詞),這就是為什麼最好在sh
腳本中引用重定向目標,以防有一天您想將其轉換為bash
腳本,或者在系統上執行sh
它在這一點上不合規,或者它可能來自互動式外殼。
- 在算術表達式中。事實上,您需要去掉引號才能將變數解析為算術表達式。
expr=2*2 echo "$(($expr))"
但是,您確實需要算術擴展周圍的引號,因為它們在大多數 shell 中會按照 POSIX 的要求進行分詞(!?)。
- 在關聯數組下標中。
typeset -A a i='foo bar*qux' a[foo\ bar\*qux]=hello echo "${a[$i]}"
在極少數情況下,未加引號的變數和命令替換可能很有用:
- 當變數值或命令輸出包含 glob 模式列表並且您希望將這些模式擴展到匹配文件列表時。
- 當您知道該值不包含任何萬用字元時,該值
$IFS
未被修改並且您希望將其拆分為空白字元。- 當您想在某個字元處拆分值時:使用 禁用萬用字元
set -f
,將其設置IFS
為分隔符(或不理會它以在空白處拆分),然後進行擴展。Zsh
在 zsh 中,大多數情況下您可以省略雙引號,但也有少數例外。
$var``var
從不擴展為多個單詞,但是如果值為空字元串,它會擴展為空列表(與包含單個空單詞的列表相反) 。對比:var= print -l $var foo # prints just foo print -l "$var" foo # prints an empty line, then foo
同樣,
"${array[@]}"
擴展到數組的所有元素,而$array
只擴展到非空元素。
- 參數擴展標誌有時需要在
@
整個替換周圍加上雙引號:"${(@)foo}"
.- 如果不加引號,命令替換會進行欄位拆分:
echo $(echo 'a'; echo '*')
列印a *
(帶有單個空格)而echo "$(echo 'a'; echo '*')"
列印未修改的兩行字元串。用於"$(somecommand)"
在一個單詞中獲取命令的輸出,沒有最後的換行符。用於"${$(somecommand; echo _)%?}"
獲取命令的準確輸出,包括最終換行符。用於"${(@f)$(somecommand)}"
從命令的輸出中獲取行數組。