SHLVL 和 BASH_SUBSHELL 兩個變數的區別

紫雲飛發表於2015-09-13

SHLVL 是記錄多個 Bash 程式例項巢狀深度的累加器,而 BASH_SUBSHELL 是記錄一個 Bash 程式例項中多個子 Shell(subshell)巢狀深度的累加器。

看不懂上面這句話不要緊,因為是我臨時編的。其實如果你混淆了這兩個變數,我猜你多半是對 BASH_SUBSHELL 這個變數名中的 subshell 概念不清,下面我們就講講什麼是 subshell,什麼不是。

很多人誤以為在 Bash 裡面再執行一次 Bash,或者再執行一個 Shell 指令碼就是進入子 Shell 了,所以他們會有下面這樣的疑問:

$ bash # 執行另外一個 bash 命令

$ echo $BASH_SUBSHELL 

$ 0 # 怎麼還是 0,我這不是在上個 Shell 的子 Shell 裡嗎?

$ echo ' echo $BASH_SUBSHELL ' > test; chmod +x test; ./test;

$ 0 # 難道再執行 Shell 指令碼也不是子 Shell?

然而並不是,這些都不是子 Shell,這種情況只能描述成是“當前 Shell 啟動了個外部命令,而這個外部命令剛好是個 Shell”,真正的子 Shell 是不需要重新執行硬碟上的外部命令的,全部是記憶體中的操作。上面這個示例中的 BASH_SUBSHELL 都應該替換成 SHLVL 才能看到累加效果。

有幾本書給子 Shell 下過定義:

Advanced Bash-Scripting Guide 說:

A subshell is a child process launched by a shell (or shell script).

Bash Cookbook 說:

A subshell is a forked copy of the parent shell and shares it’s environment.

The Korn Shell: Unix & Linux Programming Manual 說:

A subshell is a separate copy of the parent shell, so variables, functions, and aliases from the parent shell are available to the subshell

第一本書流傳較廣,但它這句話說的太泛了,很容易誤導人,雖然子 Shell 的確是當前 Shell 的子程式,但當前 Shell 的子程式不一定都是子 Shell(可能已經替換成了其他程式)。在 Bash 裡面,只有特定的語法才會讓程式碼進入子 Shell,比如管道兩邊的命令,比如用小括號括起來等等:

$ (echo $BASH_SUBSHELL)

1

$ ( ( ( ( (echo $BASH_SUBSHELL) ) ) ) )

5

真正的子 Shell 可以訪問其父 Shell 的任何變數,而通過再執行一次 bash 命令所啟動的 Shell 只能訪問其父 Shell 傳來的環境變數。這篇教程裡面專門寫了個例子:

For an example of the difference between a subshell and a child process that happens to be a shell:

unset a; a=1
(echo "a is $a in the subshell")
sh -c 'echo "a is $a in the child shell"'

In the subshell, the regular shell variable a is visible; but because it is not exported, the full child process does not see it.

上面的例子中把當前 Shell 執行外部命令 sh 啟動的 Shell 叫做 child shell,可惜在中文裡還是得翻譯成子 Shell。。。

從 c 語言層面講,真正的子 Shell 是當前 Shell 程式呼叫了 fork() 函式,在記憶體中複製出一個幾乎一模一樣的子程式。而執行 bash 命令啟動的所謂 child shell 是在執行 fork() 函式的基礎上,又執行了一次 execve() 函式,execve() 函式會重新載入硬碟上的 bash 命令並執行,替換剛才 fork 出來的那個 shell 程式,除了傳入的環境變數外,是個嶄新的程式。

總結一下就是說,SHLVL 變數是記錄了所謂的 child shell 的巢狀深度,而 BASH_SUBSHELL 是記錄了 subshell 的巢狀深度。

把 child shell 叫成子 Shell,在口頭上說說還可以,因為中文裡沒有其它什麼好的叫法用來指代它,但你心裡得明白,這不是術語子 Shell 真正的含義。

寫到這裡,我想這篇文章的標題應該改成“什麼是子 Shell”了。

相關文章