如何檢測子Shell和子進程

2020-07-16 10:04:46
上節我們說了子 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.shchmod +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。