《LINUX學習:進入子shell的各種情況分析》要點:
本文介紹了LINUX學習:進入子shell的各種情況分析,希望對您有用。如果有疑問,可以聯(lián)系我們。
子shell的概念貫穿整個shell,寫shell腳本時更是不可不知.所謂子shell,即從當前shell環(huán)境新開一個shell環(huán)境,這個新開的shell環(huán)境就稱為子shell(subshell),而開啟子shell的環(huán)境稱為該子shell的父shell.子shell和父shell的關(guān)系其實便是子進程和父進程的關(guān)系,只不過子shell和父shell是關(guān)聯(lián)的進程是bash進程.
子shell會從父shell中繼承很多環(huán)境,如變量、命令全路徑、文件描述符、當前工作目錄、陷阱等等,但子shell有很多種類型,分歧類型的子shell繼承的環(huán)境不相同.可以使用$BASH_SUBSHELL變量來查看從當前進程開始的子shell層數(shù),$BASHPID查看當前所處BASH的PID,這分歧于特殊變量"$$"值,因為"$$"會從父進程繼承.
要解釋清楚子shell以及產(chǎn)生何種類型的子shell,必要搞清楚Linux中如何產(chǎn)生子進程.Linux上創(chuàng)建子進程的方式有三種:一種是fork出來的進程,一種是exec出來的進程,一種是clone出來的進程.此處無需關(guān)心clone,因為它用來實現(xiàn)Linux中的線程.
(1).fork是復制進程,它會復制當前進程的副本(不考慮寫時復制的模式),以適當?shù)姆绞綄⑦@些資源交給子進程.所以子進程掌握的資源和父進程是一樣的,包含內(nèi)存中的內(nèi)容,所以也包含環(huán)境變量和變量.但父子進程是完全獨立的,它們是一個程序的兩個實例.
(2).exec是加載另一個應(yīng)用程序,替代當前運行的進程,也便是說在不創(chuàng)建新進程的情況下加載一個新程序.exec還有一個動作:在進程執(zhí)行完畢后,退出exec所在的shell環(huán)境.
所以為了保證進程平安,若要形成新的且獨立的子進程,都會先fork一份當前進程,然后在fork出來的子進程上調(diào)用exec來加載新程序替代該子進程.例如在bash下執(zhí)行cp命令,會先fork出一個bash,然后再exec加載cp程序覆蓋子bash進程變成cp進程.
再來說明子shell的問題.一般fork出來的子進程,內(nèi)容和父進程是一樣的(包含變量),例如執(zhí)行cp命令時也能獲取到父進程的變量.但是cp命令在哪里執(zhí)行呢?執(zhí)行cp命令敲入回車后,當前的bash進程fork出一個子bash,然后子bash通過exec加載cp程序替代子bash.這算是進入了子shell嗎?更通用的問題是:什么情況下會進入子shell環(huán)境,什么時候不進入子shel環(huán)境呢?
判斷是否進入了子shell的方式非常簡單,執(zhí)行"echo $BASHPID",如果該值和父bash進程的pid值不同,則表現(xiàn)進入了子shell.在shell中是否進入子shell的情況可以分為幾種:
①.執(zhí)行bash內(nèi)置敕令時.
bash內(nèi)置命令是非常特殊的,父進程不會創(chuàng)立子進程來執(zhí)行這些命令,而是直接在當前bash環(huán)境中執(zhí)行.但如果將內(nèi)置命令放在管道后,則此內(nèi)置命令將和管道左邊的進程同屬于一個進程組,所以仍然會創(chuàng)立子shell.
[root@linuxidc ~]# echo $BASHPID # 當前BASHPID 65230 [root@linuxidc ~]# let a=$BASHPID # bash內(nèi)置命令,不進入子shell [root@linuxidc ~]# echo $a 65230
[root@linuxidc ~]# echo $BASHPID 65230 [root@linuxidc ~]# cd | expr $BASHPID # 管道使得任何命令都進入進程組,會進入子shell 65603
②.執(zhí)行bash命令自己時.
這是一個很巧合的命令.bash命令本身是bash內(nèi)置命令,在當前shell環(huán)境下執(zhí)行內(nèi)置命令本不會創(chuàng)建子shell,也就是說不會有獨立的bash進程出現(xiàn),而實際結(jié)果則表現(xiàn)為新的bash是一個子進程.其中一個原因是執(zhí)行bash命令會加載各種環(huán)境配置項,為了父bash的環(huán)境得到掩護而不被覆蓋,所以應(yīng)該讓其以子shell的方式存在.雖然fork出來的bash子進程內(nèi)容完全繼承父shell,但因重新加載了環(huán)境配置項,所以子shell沒有繼承普通變量,更準確的說是覆蓋了從父shell中繼承的變量.不妨試試在/etc/bashrc文件中定義一個變量,再在父shell中export名稱相同值卻不同的環(huán)境變量,然后到子shell中看看該變量的值為何?
[root@linuxidc ~]# echo "var=55" >>/etc/bashrc [root@linuxidc ~]# export var=66 [root@linuxidc ~]# bash [root@linuxidc ~]# echo $var 55
由成果55可知,執(zhí)行bash時加載的/etc/bashrc中的變量覆蓋了父bash中的導出的環(huán)境變量值66.
其實執(zhí)行bash命令,既可以認為進入了子shell,也可以認為沒有進入子shell.從bash是內(nèi)置命令的角度來考慮,它不會進入子shell,這一點在執(zhí)行bash命令后從變量$BASH_SUBSHELL的值為0可以驗證出來.但從執(zhí)行bash命令后進入了新的shell環(huán)境來看,它有其父bash進程,且$BASHPID值和父shell分歧,所以它算是進入了子shell.
[root@linuxidc ~]# echo $BASHPID 65230 [root@linuxidc ~]# bash [root@linuxidc ~]# echo $BASHPID 65534
?
③.執(zhí)行shell劇本時.
腳本中第一行總是"#!/bin/bash"或者直接"bash xyz.sh",這和上面的執(zhí)行bash進入子shell其實是一回事,都是使用bash命令進入子shell.只不過此時的bash命令和情況②中直接執(zhí)行bash命令所隱含的選項不一樣,所以繼承和加載的shell環(huán)境也不一樣.事實也確實如此,它僅只繼承父shell的某些環(huán)境變量,別的環(huán)境一概初始化.
另外,執(zhí)行shell腳原形比于直接執(zhí)行bash命令,還多了一個動作:腳本執(zhí)行完畢后自動退出子shell.
[root@linuxidc ~]# cat b.sh #!/bin/bash echo $BASHPID [root@linuxidc ~]# echo $BASHPID 65534 [root@linuxidc ~]# ./b.sh 65570
?
④.執(zhí)行shell函數(shù)時.
其實shell函數(shù)便是命令,它和bash內(nèi)置命令的情況一樣.直接執(zhí)行時不會進入子shell,但放在管道后會進入子shell.
[root@linuxidc ~]# fun_test (){ echo $BASHPID; } # 定義一個函數(shù),輸出BASHPID變量的值 [root@linuxidc ~]# echo $BASHPID 65230 [root@linuxidc ~]# fun_test # 闡明執(zhí)行函數(shù)不會進入子shell 65230 [root@linuxidc ~]# cd | fun_test # 但放在管道后會進入子shell 65605
⑤.執(zhí)行非bash內(nèi)置命令時.
例如執(zhí)行cp命令、grep命令等,它們直接fork一份bash進程,然后使用exec加載程序替代該子bash.此類子進程會繼承所有父bash的環(huán)境.但嚴格地說,這已經(jīng)不是子shell,因為exec加載的程序已經(jīng)把子bash進程替換掉了,這意味著丟失了很多bash環(huán)境.在bash文檔中,直接稱謂這種環(huán)境為"單獨的環(huán)境",和子shell的概念類似.
[root@linuxidc ~]# let a=$BASHPID # let是內(nèi)置命令 [root@linuxidc ~]# echo $a 65230 [root@linuxidc ~]# echo $BASHPID # echo是非內(nèi)置命令,成果是不進入子shell 65230
?
⑥.敕令替換.
當命令行中包括了命令替換部分時,將開啟一個子shell先執(zhí)行這部分內(nèi)容,再將執(zhí)行結(jié)果返回給當前命令.因為這次的子shell不是通過bash命令進入的子shell,所以它會繼承父shell的所有變量內(nèi)容.這也就解釋了"echo $(echo $$)"中"$$"的結(jié)果是當前bash的pid號,而不是子shell的pid號,但"echo $(echo $BASHPID)"卻和父bash進程的pid不同,因為它不是使用bash命令進入的子shell.
[root@linuxidc ~]# echo $BASHPID 65230 [root@linuxidc ~]# echo $(echo $BASHPID) # 使用敕令替換$()進入子shell 65612
⑦.使用括號()組合一系列敕令.
例如(ls;date;echo haha),自力的括號將會開啟一個子shell來執(zhí)行括號內(nèi)的命令.這種情況等同于情況⑤.
[root@linuxidc ~]# echo $BASHPID 65230 [root@linuxidc ~]# (echo $BASHPID) # 使用括號()的敕令組合進入子shell 65613
⑧.放入后臺運行的任務(wù).
它不僅是一個自力的子進程,還是在子shell環(huán)境中運行的.例如"echo hahha &".
[root@linuxidc ~]# echo $BASHPID 65230 [root@linuxidc ~]# echo $BASHPID & # 放入后臺運行的任務(wù)進入子shell [1] 65614 [root@linuxidc ~]# 65614 [1]+ Done echo $BASHPID?
⑨.過程替換.
既然是新過程了,當然進入子shell執(zhí)行.例如"cat <(echo haha)".
[root@linuxidc ~]# echo $BASHPID 65230 [root@linuxidc ~]# cat <(echo $BASHPID) # 進程替換"<()"進入子shell 65616
必要說明的是,子shell的環(huán)境設(shè)置不會粘滯到父shell環(huán)境,也就是說子shell的變量等不會影響父shell.
最后,建議同時閱讀另一篇文章:bash啟動時情況配置流程,此文中詳細解釋了bash啟動時加載哪些配置文件.
本文永遠更新鏈接地址:
更多LINUX教程,盡在維易PHP學院專欄。歡迎交流《LINUX學習:進入子shell的各種情況分析》!
轉(zhuǎn)載請注明本頁網(wǎng)址:
http://www.snjht.com/jiaocheng/8851.html