[英]Strange issue resolving bash environmental variable in nested double quotes
我有一個需要在任意機器(可以是 Windows)上遠程運行的設置腳本。 所以我有一些類似於 bash -c “做需要環境變量的事情”的東西。
我發現嵌套引號+環境變量發生了一些我不理解的奇怪事情(如下所示)
# This worked because my environment was polluted.
bash -c "NAME=me echo $NAME"
> me
# I think this was a weird cross platform issue with how I was running.
# I couldn't reproduce it locally.
bash -c "NAME=me echo "Hi $NAME""
> Hi $NAME
# This was my workaround, and I have no clue why this works.
# I get that "Start "" end" does string concatenation in bash,
# but I have no clue why that would make this print 'Hi me' instead
# of 'Hi'.
#
# This works because echo Hi name prints "Hi name". I thought echo only
# took the first argument passed in.
bash -c "NAME=me echo Hi "" $NAME"
> Hi me
# This is the same as the first case. NAME was just empty this time.
bash -c "NAME=me echo Hi $NAME"
> Hi
編輯:很多人指出,在 bash -c 運行之前,變量會在雙引號中展開。 這是有道理的,但我覺得它並不能解釋為什么案例 1 有效。
不應該bash -c "NAME=me echo $NAME"
擴展到bash -c "NAME=me echo "
,因為在我們運行之前沒有設置 NAME ?
編輯2:因為我的環境被污染了,所以這些東西起作用了。 我試圖描述我在假設中犯了什么錯誤
這里至少有三個混淆來源:引號不(通常)嵌套, $variable
引用被 shell 擴展,即使它們在雙引號中,變量引用在var=value
分配完成之前解析。
我先來看第二個問題。 這是一個顯示效果的交互式示例:
$ NAME=Gordon
$ bash -c "NAME=me echo $NAME"
Gordon
在這里,外部(交互式)shell 在將$NAME
傳遞給bash -c
之前擴展了它,因此該命令本質上變成bash -c "NAME=me echo Gordon"
。 有幾種方法可以避免這種情況:您可以轉義$
以刪除其正常效果(但轉義被刪除,因此內部 shell 將看到它並正常應用它),或使用單引號而不是雙引號(刪除所有字符的特殊效果,除了結束單引號字符串的另一個單引號)。 所以讓我們試試這些:
$ bash -c "NAME=me echo \$NAME"
$ bash -c 'NAME=me echo $NAME'
(您實際上看不到它,但是第二個命令之后也有一個空行,因為它也沒有打印任何內容。)這里發生的是內部 shell (由bash -c
創建的那個)確實得到了命令NAME=me echo $NAME
,但是在執行時它首先擴展$NAME
(什么都不提供,因為它沒有在那個 shell 中定義),然后執行NAME=me echo
運行echo
命令並將NAME
設置為“me”它的環境。 讓我們以交互方式嘗試:
$ NAME=me echo $NAME
Gordon
(請記住,我之前在交互式 shell 中設置了NAME=Gordon
。)要獲得預期的效果,您需要設置NAME
,然后作為單獨的命令在echo
命令中使用它:
$ bash -c "NAME=me; echo \$NAME"
me
$ bash -c 'NAME=me; echo $NAME'
me
好的,讓我們繼續討論關於引用的原始問題。 正如我所說,引號(通常)不會嵌套。 要了解發生了什么,讓我們分析一些示例命令。 您可以更好地了解 shell 如何使用set -x
來解釋事物,這使得 shell 在執行之前打印每個命令的等效項:
$ set -x
$ bash -c "NAME=me echo "Hi $NAME""
+ bash -c 'NAME=me echo Hi' Gordon
Hi
這里發生的是 shell 將"NAME=me echo "Hi
解析為雙引號字符串,后跟兩個不帶引號的字符; 由於它們之間沒有間隙,因此它們被合並為bash -c
的單個參數。 僅引用部分參數可能看起來有點奇怪,但在 shell 語法中實際上是完全正常的。 將單個參數的一部分不加引號,部分單引號,部分雙引號,甚至是 ANSI-C 模式的一部分( $'ANSI-c-escaped stuff goes here'
)甚至是正常的。
使用set -x
, bash 將打印與正在執行的命令等效的內容。 所有這些命令在 shell 語法中是等效的:
bash -c "NAME=me echo "Hi Gordon
bash -c "NAME=me echo Hi" Gordon
bash -c 'NAME=me echo Hi' Gordon
bash -c NAME=me\ echo\ Hi Gordon
bash -c NAME=me' 'echo' 'Hi Gordon
bash -c 'NAME=me'\ "echo Hi" Gordon
...還有更多。 使用set -x
, bash 將打印其中一個等價物,並且恰好選擇在整個參數周圍帶有單引號的那個。
為了完整起見, $NAME""
發生了什么? 它被視為一個不帶引號的變量引用(擴展為Gordon
),緊隨其后的是一個長度為零的雙引號字符串,它根本不做任何事情。
但是......為什么只打印“嗨”? 好吧, bash -c
將下一個參數視為要運行的命令,並將任何進一步的 arguments 作為該命令環境的參數向量( $0
, $1
等)。 這是一個插圖:
$ bash -c 'echo "Args: $0 $1 $2"' zeroth first second third
+ bash -c 'echo "Args: $0 $1 $2"' zeroth first second third
Args: zeroth first second
(“第三”沒有被打印,因為該命令不打印$3
。)
因此,當您運行bash -c 'NAME=me echo Hi' Gordon
時,它會執行NAME=me echo Hi
並將$0
設置為“Gordon”。
好的,這是我要看的最后一個例子:
$ bash -c "NAME=me echo Hi "" $NAME"
+ bash -c 'NAME=me echo Hi Gordon'
Hi Gordon
這里發生的情況是,有一個雙引號部分"NAME=me echo Hi "
緊隨其后的是另一個" $NAME"
,因此它們被合並為一個長參數(恰好連續包含兩個空格 -第一個引用部分的一部分,第二部分的一部分)。 本質上,中間的""
結束一個雙引號部分並立即開始另一個,因此沒有整體效果。 再一次,shell 決定打印單引號等效項,而不是其他各種可能的等效項。
那么我們如何才能真正讓它正常工作呢? 以下是我實際推薦的:
$ bash -c 'NAME=me; echo "Hi $NAME"'
+ bash -c 'NAME=me; echo "Hi $NAME"'
Hi me
由於整個命令字符串都在單引號中,因此不會出現這些問題。 雙引號只是作為參數的一部分傳遞的普通字符(因此雙引號嵌套在單引號內 - 反之亦然 - 但實際上只是因為它們被忽略了),而$
沒有對外部 shell 也沒有特殊含義。 哦,還有;
使這兩個單獨的命令,因此NAME=me
部分可以在echo "$NAME"
部分使用它之前生效。
另一個等價物是:
$ bash -c "NAME=me; echo \"Hi \$NAME\""
+ bash -c 'NAME=me; echo "Hi $NAME"'
Hi me
這里的轉義刪除了$
和封閉的雙引號的特殊含義。 請注意,shell 打印的內容與上次的set -x
output 完全相同,表明這確實等同於單引號版本。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.