1. 程式人生 > 實用技巧 >Bash命令分隔符 空格和分號的區別

Bash命令分隔符 空格和分號的區別

Bash技巧:介紹“v=var echo $v”和“v=var; echo $v”這兩種寫法的區別

在 Linux bash shell 中,當在同一行裡面提供不同的命令時,命令之間需要用控制操作符隔開。

常見的控制操作符有分號 ‘;’、管道操作符 ‘|’、與操作符 ‘&&’、或操作符 ‘||’ 等。

例如,v=var; echo $v命令先把 v 變數賦值為 var,再用echo命令列印 v 變數的值。

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

WINEPREFIX=/home/.no1-wine wine /home/.no1-wine/yyyy

在這個命令裡面,為 WINEPREFIX 變數賦值的語句和後面執行的

wine命令用空格隔開,而不是用分號 ‘;’ 隔開。
當然,這個命令本身是合法命令,只是這裡為什麼要用空格隔開,而不是用分號隔開?
這種寫法跟使用分號隔開的區別是什麼呢?

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

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

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

CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure

可以看到,這個命令也是先提供變數賦值語句,再提供要執行的命令,中間用空格隔開。
在原始碼編譯其他 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 $vv=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 的線上幫助手冊,找到這個寫法對應的說明,可謂是因小見大、查缺補漏了。



採用《署名-非商業性使用-禁止演繹 4.0 國際》許可協議來源:https://segmentfault.com/a/1190000022100661?utm_source=sf-related


來自為知筆記(Wiz)