Bash

什麼時候需要雙引號?

  • June 22, 2017

過去的舊建議是對涉及 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在所有這些情況下都可以省略雙引號。

但是從上述軼事範例中推斷出一般規則是一個機會命題。很高興看到何時需要雙引號的摘要。我主要對zshbash和感興趣/bin/sh

首先,將 zsh 與其他部分分開。這不是舊 shell 與現代 shell 的問題:zsh 的行為不同。zsh 設計者決定使其與傳統的 shell(Bourne、ksh、bash)不兼容,但更易於使用。

其次,始終使用雙引號比記住何時需要它們要容易得多。大多數時候都需要它們,因此您需要在不需要它們時學習,而不是在需要它們時學習。

簡而言之,在需要單詞列表或模式的任何地方都需要雙引號。在解析器需要原始字元串的上下文中,它們是可選的。

沒有引號會發生什麼

請注意,如果沒有雙引號,會發生兩件事。

  1. 首先,擴展的結果(參數替換的變數值,如${foo},或命令替換的命令輸出,如$(foo))被拆分為包含空格的單詞。

更準確地說,擴展的結果在IFS變數值中出現的每個字元處拆分(分隔符)。如果分隔符序列包含空格(空格、製表符或換行符),則將空格計為單個字元;前導、尾隨或重複的非空白分隔符會導致空欄位。例如, with IFS=" :",:one::two : three: :four產生空欄位 before one, between oneand two, and (一個單一的) between threeand four。 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, nor ksh88)中的重定向。
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)}"從命令的輸出中獲取行數組。

引用自:https://unix.stackexchange.com/questions/68694