簡體   English   中英

解決嵌套雙引號中的 bash 環境變量的奇怪問題

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM