Bash技巧:介紹v=var echo $v 和 v=var; echo $v的區別


Bash技巧:介紹v=var  echo $v 和 v=var; echo $v的區別

在 Linux bash shell 中,當在同一行裡面提供不同的命令時,命令之間需要用控制操作符隔開。
常見的控制操作符有分號 ‘;’、管道操作符 ‘|’、與操作符 ‘&&’、或操作符 ‘||’ 等。
例如,v=var; echo $v 命令先把 v 變量賦值為 var,再用 echo 命令打印 v 變量的值。

但是今天在查看安裝 wine 命令的文章時,裡面提供瞭如下的命令寫法:

<code>WINEPREFIX=/home/.no1-wine wine /home/.no1-wine/yyyy
/<code>

在這個命令裡面,為 WINEPREFIX 變量賦值的語句和後面執行的 wine 命令用空格隔開,而不是用分號 ‘;’ 隔開。
當然,這個命令本身是合法命令,只是這裡為什麼要用空格隔開,而不是用分號隔開?
這種寫法跟使用分號隔開的區別是什麼呢?

本著鑽研精神,通過查看 GNU bash 的在線幫助手冊,找到了這種寫法的相關說明。具體介紹如下。

GNU bash 在線幫助手冊的鏈接是 http://www.gnu.org/software/bash/manual/bash.html。
後面貼出的英文說明都出自這個在線幫助鏈接。
這是 GNU bash 的標準手冊,權威可靠。

在 GNU bash 在線幫助手冊裡面,也用到了類似上面命令的寫法。
在 “10.2 Compilers and Options” 小節提供的編譯 bash 命令如下:

<code>CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure
/<code>

可以看到,這個命令也是先提供變量賦值語句,再提供要執行的命令,中間用空格隔開。
在源碼編譯其他 Linux 軟件時,也會用到類似的寫法。

Bash 的簡單命令

在 GNU bash 在線幫助手冊的 “3.2.1 Simple Commands” 小節介紹了簡單命令的概念:

A simple command is the kind of command encountered most often.
It’s just a sequence of words separated by blanks, terminated by one of the shell’s control operators (see Definitions).
The first word generally specifies a command to be executed, with the rest of the words being that command’s arguments.

這裡面提到,簡單命令是一串用空白字符隔開的單詞,由 shell 的控制操作符(control operator)所終止。
一般來說,簡單命令的第一個單詞就是要執行的命令,後面跟著的單詞是該命令的參數。

可以終止簡單命令的控制操作符要查看 “2 Definitions” 小節,具體說明如下:

control operator
A token that performs a control function.


It is a newline or one of the following: ‘||’, ‘&&’, ‘&’, ‘;’, ‘;;’, ‘;&’, ‘;;&’, ‘|’, ‘|&’, ‘(’, or ‘)’.

如前面說明,常見的控制操作符有分號 ‘;’、管道操作符 ‘|’、與操作符 ‘&&’、或操作符 ‘||’ 等。

結合這兩個說明,一般來說,簡單命令以命令名開頭,以控制操作符結尾。
不同的簡單命令之間要用控制操作符或者換行符隔開。

但是在上面提供的 CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure 命令中,賦值語句和要執行命令之間沒有用控制操作符隔開。
這就比較奇怪。這也是本篇文章所要討論的問題。

Bash 的環境變量

在 GNU bash 在線幫助手冊的 “3.7.4 Environment” 小節裡面,介紹了先提供變量賦值語句、再提供被執行命令這個寫法的作用。
具體說明如下:

When a program is invoked it is given an array of strings called the environment.
This is a list of name-value pairs, of the form name=value.
The environment for any simple command or function may be augmented temporarily by prefixing it with parameter assignments, as described in Shell Parameters.
These assignment statements affect only the environment seen by that command.

可以看到,bash 在執行命令時,會為執行命令的進程準備一些環境變量。
環境變量是由 name=value 這種形式的列表組成。

在簡單命令前面提供變量賦值語句,可以在執行該命令時提供臨時的環境變量
所給的變量賦值語句隻影響執行該命令時的環境。

這就是 CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure 這種命令寫法的作用所在。
這個命令為 CC、CFLAGS、LIBS 這三個變量賦值,且把這三個變量賦值語句添加到執行 ./configure 命令時的環境裡面。
那麼,./configure 命令就可以通過 CC、CFLAGS、LIBS 這三個變量名來獲取對應的值。
這三個賦值語句隻影響執行 ./configure 命令時的環境,不影響當前 shell 的環境。
也就是說,在當前 shell 中並沒有定義 CC、CFLAGS、LIBS 這三個變量。

如果寫成 CC=c89 CFLAGS=-O2 LIBS=-lposix; ./configure 的形式,用分號 ‘;’ 隔開賦值語句和被執行的命令。
如前面說明,分號會終止一個簡單命令。
那麼賦值語句和被執行的命令之間是兩個簡單命令,擁有各自不同的進程環境。
執行 ./configure 命令時的環境變量沒有包含 CC、CFLAGS、LIBS 這三個變量。

簡單命令的擴展順序

在 GNU bash 在線幫助手冊的 “3.7.1 Simple Command Expansion” 小節裡面有如下說明:

When a simple command is executed, the shell performs the following expansions, assignments, and redirections, from left to right.

1. The words that the parser has marked as variable assignments (those preceding the command name) and redirections are saved for later processing.

If no command name results, the variable assignments affect the current shell environment.
Otherwise, the variables are added to the environment of the executed command and do not affect the current shell environment.

可以看到,當執行一個簡單命令時,命令名前面的變量賦值語句會被標識起來,留待後面處理。
也就是說,在變量名前面提供變量賦值語句,確實是合法有效的寫法。

如果變量賦值語句後面沒有跟著任何命令名,那麼這個賦值語句會影響當前 shell 環境。
即,會在當前 shell 中定義所賦值的變量。該變量在當前 shell 中可見。

如果變量賦值語句後面跟著命令名,則這個變量會被添加到運行該命令時的環境變量裡面,且不會影響當前 shell 環境。
即,在當前 shell 中沒有定義所賦值的變量。該變量在當前 shell 中不可見。

環境變量在子 shell 中的繼承關係

在 GNU bash 在線幫助手冊的 “3.7.3 Command Execution Environment” 小節裡面有如下說明:

When a simple command other than a builtin or shell function is to be executed, it is invoked in a separate execution environment that consists of the following.
Unless otherwise noted, the values are inherited from the shell.


- shell variables and functions marked for export, along with variables exported for the command, passed in the environment (see Environment)

可以看到,bash 會在一個單獨的執行環境中執行簡單命令,並從父 shell 中繼承一些值。
其中,父 shell 裡面定義的變量,默認不會被子 shell 繼承。
只有經過 export 命令導出的變量才會被子 shell 繼承。

驗證 “v=var echo $v” 和 “v=var; echo $v” 命令的區別

基於前面說明,可知 “v=var echo $v” 和 “v=var; echo $v” 命令之間的區別在於,執行命令時的環境變量有所不同。
具體測試如下:

$ v=var echo $v

$ v=var; echo $v

var

$ v=var env | grep var

v=var

$ v=var; env | grep var


可以看到,v=var echo $v 命令打印的結果為空。


在這個命令中,定義了一個 v 變量,並把這個變量添加到執行 echo $v 命令的環境變量裡面。
則 echo 命令可以通過 v 這個變量名來獲取到對應的值。
但是 echo 命令自身的代碼沒有獲取 v 這個變量值,所以沒有影響。
這裡的 echo $v 命令是獲取當前 shell 裡面的 v 變量值,作為參數傳遞給 echo 命令。
由於這種寫法定義的 v 變量在當前 shell 中不可見,所以獲取到的值為空。
最終打印結果為空。

而 v=var; echo $v 命令打印了 v 變量的值。
這裡在 v=var 之後加了分號 ‘;’,讓 v=var 成為一個單獨的簡單命令。
基於前面說明,v 變量在當前 shell 中可見。
之後 echo $v 命令能夠在當前 shell 中獲取到 v 變量值,作為參數傳遞給 echo 命令。
echo 命令收到傳入的參數值,打印出 “var” 字符串。

進一步驗證,v=var env | grep var 命令用 env 命令打印出運行時的環境變量,並過濾出 var 關鍵字。
可以看到,打印出來的環境變量中包含了 v=var 這個賦值語句。
這個打印結果和前面說明相符。在命令前面提供變量賦值語句,變量會添加到執行命令時的環境變量裡面。

而 v=var; env | grep var 命令的打印結果為空。
這個命令雖然在當前 shell 中定義了 v 變量,但是 v 變量沒有添加到當前 shell 的環境變量裡面。
所以 env 的打印裡面沒有包含 v=var 這個賦值語句。

驗證 “v=var ./test.sh” 和 “v=var; ./test.sh” 命令的區別

由於 echo 命令自身的代碼沒有獲取 v 這個變量值,不能明顯看到 v 變量添加到環境變量後的測試結果。

假設有一個 test.sh 腳本,內容如下:

#!/bin/bash

echo $v

這個腳本打印一個 v 變量的值。但是腳本自身沒有定義 v 變量。

使用這個腳本進行測試的結果如下:

$ v=var ./test.sh

var

$ v=var; ./test.sh


可以看到,v=var ./test.sh 命令打印出 v 變量對應的值。
雖然 test.sh 腳本自身沒有定義 v 變量,但是執行時在命令名前面提供了 v=var 變量賦值語句。


這會把 v 變量添加到了執行 test.sh 腳本時的環境變量裡面,讓 test.sh 腳本獲取到了 v 變量的值。

而 v=var; ./test.sh 命令打印為空。
這種寫法是在當前 shell 中定義 v 變量。
基於前面說明的“環境變量在子 shell 中的繼承關係”,可知這個 v 變量不會被子 shell 繼承。
所以執行 test.sh 腳本時,不會獲取父 shell 裡面的 v 變量值。
而 test.sh 腳本自身又沒有定義 v 變量,所以打印結果為空。

最後,修改 test.sh 腳本為如下內容,讓該腳本自身定義 v 變量:

#!/bin/bash

v=init

echo $v

再次測試的結果如下:

$ v=var ./test.sh

init

$ v=var; ./test.sh

init

可以看到,當 test.sh 腳本自身定義了 v 變量時,以 test.sh 腳本定義的值為準,不受環境變量的影響。

結語

總的來說,在命令名前面提供變量賦值語句,且變量賦值語句和命令名之間用空格隔開時,所給的變量賦值語句會添加到執行命令時的環境變量裡面,且不影響當前 shell 的執行環境。

本篇文章的發起點從偶然看到一個 WINEPREFIX=/home/.no1-wine wine /home/.no1-wine/yyyy 命令開始,敏銳地察覺到這個寫法的怪異之處。
沒有輕易放過這個疑問,通過查看 GNU bash 的在線幫助手冊,找到這個寫法對應的說明,可謂是因小見大、查缺補漏了。


分享到:


相關文章: