當我在命令前面加上「一次性變數賦值」前綴時,為什麼 bash 不擴展此變數

當我在命令前面加上「一次性變數賦值」前綴時,為什麼 bash 不擴展此變數

如果我執行此 bash 命令並為語句添加前綴,則該變數fruit應該存在,但僅在此命令的持續時間內:

$ fruit=apple echo $fruit

$

結果是一個空白行。為什麼?

引用通配符的評論這個問題

參數擴展是由shell完成的,「fruit」變數不是shell變數;它只是“echo”命令環境中的一個環境變量

環境變量仍然是一個變量,所以這肯定仍然可以用於 echo 命令嗎?

答案1

問題是當前 shell 過早地擴展了變數;它沒有在其上下文中設置,因此該echo命令沒有任何參數,即命令最終是:

$ fruit=apple echo

這是一種解決方法,由於單引號,變數不會過早擴展:

$ fruit=apple sh -c 'echo $fruit'

或者,您也可以使用單行 shell 腳本,該腳本示範fruit變數已正確傳遞給執行的命令:

$ cat /tmp/echof
echo $fruit
$ /tmp/echof

$ fruit=apple /tmp/echof
apple
$ echo $fruit

$

由於這個問題引發了一些意想不到的爭議和討論,一些評論如下:

  • 變數fruit是否已導出這一事實並不影響其行為,重要的是在 shell 擴展它的精確時刻變數值是什麼。
$ 出口水果=香蕉
$水果=蘋果迴音$水果
香蕉
  • 該命令是內建命令這一事實echo不會影響 OP 問題。但是,在某些情況下,使用具有此語法的內建函數或 shell 函數會產生意想不到的副作用,例如:
$ 出口水果=香蕉
$fruit=apple eval 'echo $fruit'
蘋果
$迴音$水果
蘋果
  • 雖然兩者之間有相似之處這裡問的問題和那個,這不是完全相同的問題。對於另一個問題,IFS當 shell分詞 其他$var此處的變量,fruit當 shell 執行時臨時變數值尚不可用擴大相同的多變的。

  • 還有另一個問題OP 詢問所使用語法的重要性,更準確地說是詢問「為什麼這樣做?」。在這裡,OP 意識到了其重要性,但報告了意外行為並詢問其原因,即「為什麼這不起作用?」。好的,在更仔細地閱讀了另一個問題上發布的糟糕的屏幕截圖後,確實在那裡描述了相同的情況(BAZ=jake echo $BAZ)所以是的,畢竟這是重複的

答案2

為了正確理解這一點,我們首先要區分外殼變數環境變數。

環境變數是所有進程都擁有的屬性,無論它們是否在內部使用它們。即使sleep 10它運行時也有環境變數。正如所有進程都有 PID(進程識別碼)、目前工作目錄 (cwd)、PPID(父 PID)、參數清單(即使為空)等。所有進程也都有所謂的“環境”,它是在分叉時從父進程繼承的。

從實用程式作者(用 C 語言編寫程式碼的人)的角度來看,進程能夠設定、取消設定或更改環境變數。然而,從腳本作者的角度來看,大多數工具並未向使用者提供該功能。相反,您使用您的變更進程環境,然後在執行您呼叫的命令(外部二進位)時繼承該進程環境。 (您的 shell 本身的環境可以修改並繼承修改,或者您可以指示您的 shell 在 fork 之後但在執行您調用的命令之前進行修改。無論哪種方式,環境都是繼承的。我們將同時查看接近。

shell 變數是另一回事。雖然殼內它們的行為方式相同,差異在於單純的「shell 變數」不會改變或影響您呼叫的命令的行為你的外殼。用正確的術語來說,這種區別實際上會有一點不同的表達。出口shell 變數將成為您呼叫的工具環境的一部分,而 shell 變數不是導出不會。但是,我發現引用未導出為「shell 變數」的 shell 變數和導出為「shell 變數」的 shell 變數對通訊更有幫助。導出為“環境變數”,因為它們從 shell 派生的進程的角度來看環境變數。


這是很多文字。讓我們來看一些例子並描述正在發生的事情:

$ somevar=myfile
$ ls -l "$somevar"
-rw-r--r--  1 Myname  staff  0 May 29 19:12 myfile
$ 

在這個例子中,somevar只是一個shell變量,沒有什麼特別的。殼參數擴充(見LESS='+/Parameter Expansion' man bash)發生可執行ls檔實際上已載入(“exec”),而ls命令(進程)甚至從未被載入看到字串“美元符號 somevar”。它只看到字串“myfile”,將其解釋為當前工作目錄中文件的路徑,並獲取並列印有關它的資訊。

如果我們在命令export somevar之前運行ls,那麼事實somevar=myfile將出現在環境進程的ls,但這不會影響任何事情,因為該ls命令不會對此變數執行任何操作。去看一個影響對於環境變量,我們必須選擇一個環境變量,我們調用的進程將實際檢查並執行某些操作。


bc:基本計算器

可能有更好的例子,但這是我想出來的一個並不太複雜的例子。首先您應該知道這bc是基本計算器,可以處理和計算數學表達式。 (處理完任何輸入文件的內容後,它會處理其標準輸入。我不會在示例中使用其標準輸入;我只需按 Ctrl-D,這不會在下面的文本片段中顯示。另外我用來-q在每次調用時抑制介紹性訊息。

我將說明的環境變數描述如下man bc

   BC_ENV_ARGS
      This is another mechanism to get arguments to bc.  The format is
      the  same  as  the  command line arguments.  These arguments are
      processed first, so any files listed in  the  environment  argu-
      ments  are  processed  before  any  command line argument files.
      This allows the user to set up "standard" options and  files  to
      be  processed at every invocation of bc.  The files in the envi-
      ronment variables would typically contain  function  definitions
      for functions the user wants defined every time bc is run.

開始:

$ cat file1
5*5
$ bc -q file1
25
$ cat file2
6*7
8+9+10
$ bc -q file2
42
27
$ bc -q file1 file2
25
42
27
$

這只是為了展示如何bc工作。在每種情況下,我都必須按 Ctrl-D 向 發出「輸入結束」訊號bc

現在,讓我們傳遞一個環境變量直接地bc

$ BC_ENV_ARGS=file1 bc -q file2
25
42
27
$ echo "$BC_ENV_ARGS"

$ bc -q file2
42
27
$

請注意,我們在該變數中放入的內容是不是稍後可以透過echo命令看到。透過將賦值作為相同指令的一部分(也沒有分號),我們將該變數賦值為部分的環境bc- 它使我們正在運行的 shell 本身不受影響。

現在讓我們設定BC_ENV_ARGS多變的:

$ BC_ENV_ARGS=file1
$ echo "$BC_ENV_ARGS"
file1
$ bc -q file2
42
27
$

在這裡你可以看到我們的echo命令可以看到內容,但它不是環境的一部分,bc所以bc不能用它做任何特殊的事情。

當然,如果我們將變數本身放入bc的參數列表中,我們會看到一些內容:

$ bc -q "$BC_ENV_ARGS"
25
$ 

但在這裡,是展開變量,然後file1就是實際出現在bcs中的內容參數列表。 所以這仍然將其用作shell變量,而不是環境變量。

現在讓我們「導出」這個變量,使其既是一個 shell 變數又是一個環境變數:

$ export BC_ENV_ARGS
$ echo "$BC_ENV_ARGS"
file1
$ bc -q file2
25
42
27
$

在這裡您可以看到它file1是在之前處理的file2,儘管這裡的命令行中沒有提到。它是 shell 環境的一部分,bc當您執行該進程時成為 的環境的一部分,因此該環境變數的值為遺傳並影響bc運作方式。

我們仍然可以在每個命令的基礎上重寫它,甚至將它重寫為空值:

$ BC_ENV_ARGS= bc -q file2
42
27
$ echo "$BC_ENV_ARGS"
file1
$ bc -q file2
25 
42
27
$ 

但正如您所看到的,該變數在我們的 shell 中保持設定和匯出狀態,對於 shell 本身以及任何後續bc命令都可見覆蓋該值。除非我們“取消匯出”或“取消設定”它,否則它將保持這種狀態。我會做後者:

$ unset BC_ENV_ARGS
$ echo "$BC_ENV_ARGS"

$ bc -q file2
42
27
$ 

另一個例子,涉及產生另一個 shell:

在 shell 中依序鍵入以下命令並考慮結果。在運行之前看看您是否可以預測結果。

# fruit is not set
echo "$fruit"
sh -c 'echo "$fruit"'
# fruit is set as a shell variable in the current shell only
fruit=apple
echo "$fruit"
sh -c 'echo "$fruit"'
sh -c "echo $fruit" ### NOT advised for use in scripts, for illustration only
# fruit is exported, so it's accessible in current AND new processes
export fruit
echo "$fruit"
sh -c 'echo "$fruit"'
echo '$fruit' ### I threw this in to make sure you're not confused on quoting
# fruit is unset again
unset fruit
echo "$fruit"
sh -c 'echo "$fruit"'
# setting fruit directly in environment of single command but NOT in current shell
fruit=apple sh -c 'echo "$fruit"'
echo "$fruit"
fruit=apple echo "$fruit"
# showing current shell is unaffected by directly setting env of single command
fruit=cherry
echo "$fruit"
fruit=apricot sh -c 'echo "$fruit"'
echo "$fruit"
sh -c 'echo "$fruit"'

最後一個是額外的技巧:你能預測以下按順序運行的命令的輸出嗎? :)

fruit=banana
fruit=orange sh -c 'fruit=lemon echo "$fruit"; echo "$fruit"; export fruit=peach'
echo "$fruit"

請在評論中提及任何需要的澄清;我確信這可能會用到一些。但即便如此,它也應該有幫助。

答案3

因為命令列上的擴展發生在變數賦值之前。標準是這麼說的

當需要執行給定的簡單命令時,應執行以下操作:

  1. 被識別為變數賦值的單字將被保存以供在步驟 3 和 4 中處理。

  2. 不是變數賦值或重定向的單字應該被擴展。 [...]

  3. 重定向應按照重定向中的描述執行。

  4. 每個變數賦值應在賦值之前展開[...]。

注意順序:在第一步驟中,作業僅已儲存,然後擴展其他單字,並且只有最後才進行變數賦值。

當然,標準很可能這麼說只是因為它一直都是這樣,而且他們只是將現有的行為編入法典。它沒有提及任何有關歷史或背後原因的內容。 shell 必須識別在某些時候看起來像賦值的單字(如果只是為了不將它們作為命令的一部分),所以我認為它可以按其他順序工作,首先分配變量,然後在命令列上展開任何內容。 (或只是從左到右進行......)

相關內容