上節我們說了子 Shell 和子進程的區別,這節就來看一下如何檢測它們。
我們都知道使用 $ 變數可以獲取當前進程的 ID,我在父 Shell 和子 Shell 中都輸出 $ 的值,只要它們不一樣,不就是建立了一個新的進程嗎?那我們就來試一下吧。
[[email protected] ~]$ echo $$ #父Shell PID
3299
[[email protected] ~]$ (echo $$) #組命令形式的子Shell PID
3299
[[email protected] ~]$ echo "http://c.biancheng.net" | { echo $$; } #管道形式的子Shell PID
3299
[[email protected] ~]$ read < <(echo $$) #進程替換形式的子Shell PID
[[email protected] ~]$ echo $REPLY
3299
你看,子 Shell 和父 Shell 的 ID 都是一樣的,哪有產生新進程了?作者你是不是騙人呢?
其實不是我騙人,而是你掉坑裡了,因為 $ 變數在子 Shell 中無效!Base 官方文件說,在普通的子進程中,$ 確實被展開為子進程的 ID;但是在子 Shell 中,$ 卻被展開成父進程的 ID。
除了 $,Bash 還提供了另外兩個環境變數——SHLVL 和 BASH_SUBSHELL,用它們來檢測子 Shell 非常方便。
SHLVL 是記錄多個 Bash 進程範例巢狀深度的累加器,每次進入一層普通的子進程,SHLVL 的值就加 1。而 BASH_SUBSHELL 是記錄一個 Bash 進程範例中多個子 Shell(sub shell)巢狀深度的累加器,每次進入一層子 Shell,BASH_SUBSHELL 的值就加 1。
1) 我們還是用範例來說話吧,先說 SHLVL。建立一個指令碼檔案,命名為 test.sh,內容如下:
#!/bin/bash
echo "$SHLVL $BASH_SUBSHELL"
然後開啟 Shell 視窗,依次執行下面的命令:
[[email protected] ~]$ echo "$SHLVL $BASH_SUBSHELL"
2 0
[[email protected] ~]$ bash #執行bash命令開啟一個新的Shell對談
[[email protected] ~]$ echo "$SHLVL $BASH_SUBSHELL"
3 0
[[email protected] ~]$ bash ./test.sh #通過bash命令執行指令碼
4 0
[[email protected] ~]$ echo "$SHLVL $BASH_SUBSHELL"
3 0
[[email protected] ~]$ chmod +x ./test.sh #給指令碼增加執行許可權
[[email protected] ~]$ ./test.sh
4 0
[[email protected] ~]$ echo "$SHLVL $BASH_SUBSHELL"
3 0
[[email protected] ~]$ exit #退出內層Shell
exit
[[email protected] ~]$ echo "$SHLVL $BASH_SUBSHELL"
2 0
SHLVL 和 BASH_SUBSHELL 的初始值都是 0,但是輸出結果中 SHLVL 的值從 2 開始,我猜測 Bash 在初始化階段可能建立了子進程,我們暫時不用理會它,將關注點放在值的變化上。
仔細觀察的讀者應該會發現,使用 bash 命令開啟新的對談後,需要使用 exit 命令退出才能回到上一級 Shell 對談。
bash ./test.sh
和
chmod +x ./test.sh; ./test.sh
這兩種執行指令碼的方式,在指令碼執行期間會開啟一個子進程,執行結束後立即退出子進程。
2) 再說一下 BASH_SUBSHELL,請看下面的命令:
[[email protected] ~]$ echo "$SHLVL $BASH_SUBSHELL"
2 0
[[email protected] ~]$ (echo "$SHLVL $BASH_SUBSHELL") #組命令
2 1
[[email protected] ~]$ echo "hello" | { echo "$SHLVL $BASH_SUBSHELL"; } #管道
2 1
[[email protected] ~]$ var=$(echo "$SHLVL $BASH_SUBSHELL") #命令替換
[[email protected] ~]$ echo $var
2 1
[[email protected] ~]$ ( ( ( (echo "$SHLVL $BASH_SUBSHELL") ) ) ) #四層組命令
2 4
你看,組命令、管道、命令替換這幾種寫法都會進入子 Shell。
注意,“進程替換”看起來好像產生了一個子 Shell,其實只是玩了一個障眼法而已。進程替換只是借助檔案在
()
內部和外部的命令之間傳遞資料,但是它並沒有建立子 Shell;換句話說,
()
內部和外部的命令是在一個進程(也就是當前進程)中執行的。
我們不妨來實際檢測一下:
[[email protected] ~]$ echo "$SHLVL $BASH_SUBSHELL"
2 0
[[email protected] ~]$ echo "hello" > >(echo "$SHLVL $BASH_SUBSHELL")
2 0
SHLVL 和 BASH_SUBSHELL 變數的值都沒有發生改變,說明進程替換既沒有進入子進程,也沒有進入子 Shell。