子shell及shell巢狀模式知識
出處:《跟老男孩學Linux:Shell程式設計實戰》
子shell的知識及實踐說明
什麼是子shell
子shell的本質可以理解為shell的子程序,子程序的概念是由父程序的概念引申而來的,在Linux系統中,系統執行的應用程式幾乎都是從init(pid為1的程序)程序派生而來的,所有這些應用程式都可以視為init程序的子程序,而init則為它們的父程序,通過執行pstree -a命令就可以看到init及系統中其他程序的程序樹資訊。
子shell的常見產生途徑及特點
1.帶“&”提交後臺作業
下面通過shell指令碼來實現一個由“&”產生的子shell,指令碼如下:
#! /bin/bash
parent_var="Parent"
#<==定義父shell變數parent_var並賦值標誌性字元
echo "Shell Start: ParentShell Level: $BASH_SUBSHELL"
#<==輸出父shell層級,BASH_SUBSHELL為系統環境變數
{
echo "SubShell Level: $BASH_SUBSHELL"
#<==輸出子shell的層級,和父shell層級對比
sub_ver="Sub"
echo "parent_var=$parent_var"
sleep 2
echo "Subshell is over."
} & #<==由“&”產生的子shell
echo "Now ParentShell start again."
echo "Shell Over: ParentShell Level: $BASH_SUBSHELL"
if [ -z "$sub_var" ];then
echo "sub_var is not defined in ParentShell"
else
echo "sub_var is defined in ParentShell"
fi
Shell Start: ParentShell Level: 0 Now ParentShell start again. Shell Over: ParentShell Level: 0 SubShell Level: 1 sub_var is not defined in ParentShell parent_var=Parent Subshell is over.
結論:
- 在shell中使用“&”可以產生子shell。
- 由“&”產生的子shell可以直接引用父shell定義的本地變數。
- 由“&”產生的子shell中定義的變數不能被父shell引用。
- 在shell中使用“&”可以實現其他程式的多執行緒併發功能。
2.使用“管道”功能
#! /bin/bash
parent_var="Parent"
echo "Shell Start: ParentShell Level: $BASH_SUBSHELL"
echo "" | \ #<==管道 換行
{
echo "SubShell Level: $BASH_SUBSHELL"
sub_ver="Sub"
echo "parent_var=$parent_var"
sleep 2
echo "Subshell is over."
} #<==去掉了&符號
echo "Now ParentShell start again."
echo "Shell Over: ParentShell Level: $BASH_SUBSHELL"
if [ -z "$sub_var" ];then
echo "sub_var is not defined in ParentShell"
else
echo "sub_var is defined in ParentShell"
fi
Shell Start: ParentShell Level: 0
SubShell Level: 1
parent_var=Parent
Subshell is over.
Now ParentShell start again.
Shell Over: ParentShell Level: 0
sub_var is not defined in ParentShell
結論:
- 在shell中使用管道可以產生子shell。
- 由管道產生的子shell可以直接引用父shell定義的本地變數。
- 由管道產生的子shell中定義的變數不能被父shell引用。
- 由管道產生的子shell不能非同步執行,只能在執行完畢後才能返回到父shell環境。(在shell中使用管道不可以實現其他程式的多執行緒併發功能。)
3.使用“()”功能
#! /bin/bash
parent_var="Parent"
echo "Shell Start: ParentShell Level: $BASH_SUBSHELL"
( #<==使用小括號,將命令集括起來
echo "SubShell Level: $BASH_SUBSHELL"
sub_ver="Sub"
echo "parent_var=$parent_var"
sleep 2
echo "Subshell is over."
) #<==小括號結束
echo "Now ParentShell start again."
echo "Shell Over: ParentShell Level: $BASH_SUBSHELL"
if [ -z "$sub_var" ];then
echo "sub_var is not defined in ParentShell"
else
echo "sub_var is defined in ParentShell"
fi
Shell Start: ParentShell Level: 0
SubShell Level: 1
parent_var=Parent
Subshell is over.
Now ParentShell start again.
Shell Over: ParentShell Level: 0
sub_var is not defined in ParentShell
結論:
- 在shell中使用()可以產生子shell。
- 由()產生的子shell可以直接引用父shell定義的本地變數。
- 由()產生的子shell中定義的變數不能被父shell引用。
- 由()產生的子shell不能非同步執行,只能在執行完畢後才能返回到父shell環境。(在shell中使用管道不可以實現其他程式的多執行緒併發功能。)
4.通過呼叫外部shell指令碼產生子shell
父指令碼
#! /bin/bash
parent_var="Parent"
export parent_env_var="Parent Env"
echo "Shell Start: ParentShell Level: $BASH_SUBSHELL"
sh ./20_1_4_SubShell.sh
sleep 1
echo "Now ParentShell start again."
echo "Shell Over: ParentShell Level: $BASH_SUBSHELL"
if [ -z "$sub_var" ];then
echo "sub_var is not definded in ParentShell"
else
echo "sub_var is defined in ParentShell"
fi
子指令碼
#! /bin/bash
echo "SubShell Level: $BASH_SUBSHELL"
sub_var="Sub"
echo "sub_var=$sub_var"
echo "parent_var=$parent_var"
echo "parent_env_var=$parent_env_var"
sleep 2
echo "SubShell is over."
執行結果
Shell Start: ParentShell Level: 0
SubShell Level: 0
sub_var=Sub
parent_var= #<==子shell無法引用父shell變數,因此等號後面為空
parent_env_var=Parent Env #<==子shell可以引用父shell定義的全域性環境變數
SubShell is over.
Now ParentShell start again.
Shell Over: ParentShell Level: 0
sub_var is not definded in ParentShell
結論:
- 呼叫外部shell指令碼產生的子shell可以直接引用父shell定義的環境變數
- 呼叫外部shell指令碼產生的子shell中定義的變數(或者環境變數)不能被父shell引用
- 呼叫外部shell指令碼產生的子shell不能非同步執行,只有執行完畢後才能返回到父shell環境
shell呼叫指令碼的模式說明
在主指令碼中巢狀指令碼的方式有很多,常見的為fork、exec、source三種模式,這三種呼叫指令碼的方式有一定的區別。
fork模式呼叫指令碼知識
fork模式是最普通的指令碼呼叫方式,及直接在父腳本里面用“/bin/bash /directory/script.sh”來呼叫指令碼,或者在命令列中給script.sh指令碼檔案設定執行許可權,然後使用/directory/script.sh來呼叫指令碼。
使用上述方式呼叫指令碼的時候,系統會開啟一個SubShell(子shell)執行呼叫的指令碼,SubShell執行的時候ParentShell還在,SubShell執行完畢後返回到ParentShell。最後的結論是SubShell可以從ParentShell繼承環境變數,但是預設情況下SubShell中的環境變數不能帶回ParentShell。
執行方式說明:
/directory/script.sh
#<== 對指令碼賦予執行許可權,直接執行指令碼。
/bin/bash /directory/script.sh
#<== 在不賦予執行許可權時,利用執行直譯器執行
exec模式呼叫指令碼
exec模式與fork模式呼叫指令碼的方式不同,不需要新開一個SubShell來執行被呼叫的指令碼。被呼叫的指令碼與父指令碼在同一個shell內執行,但是使用exec呼叫一個新指令碼以後,父指令碼中的exec執行之後的指令碼內容就不會再執行了,這就是exec和source的區別。
執行方式說明:
exec /directory/script.sh
source模式呼叫指令碼
source模式與fork模式的區別是不會新開一個SubShell來執行被呼叫的指令碼,而是在同一個shell中執行,所以在被呼叫的指令碼中聲名的變數和環境變數都可以在主(父)指令碼中獲取和使用。
source模式與exec模式相比,最大的不同之處是使用source呼叫一個新指令碼以後,父指令碼中source命令列之後的內容在自指令碼執行完畢後依然會被執行。
執行方式說明:
source /directory/script.sh
#<== 使用source不容易被誤解,而“.”和“./”相近,容易被誤解。
. /directory/script.sh
#<== “.”和source命令的功能是等價的。
對比fork模式與source模式的區別
對於fork模式,有以下結論:
- 父指令碼執行後的PID資訊與巢狀指令碼(子shell指令碼)執行後的PID資訊不同,說明fork模式呼叫指令碼確實產生了子shell
- 父指令碼的變數資訊會被巢狀如的指令碼(子shell)引用,“SubShell.sh get$ParentVar=Parent”中等號右邊的Parent即為呼叫父指令碼變數後輸出的結果。
- 在嵌入的指令碼(子shell)中定義的變數資訊無法被父指令碼引用,“ParentShell.sh : Get : $SUB_VAR=”中等號右邊內容為空,表示沒有引用到父指令碼的變數。
對於source模式,有以下結論:
- 父指令碼執行後的PID資訊與巢狀指令碼(子shell指令碼)執行後的PID資訊一致,說明source模式呼叫指令碼不會產生子shell,而是在同一個shell裡執行,此項與fork模式不同。
- 在嵌入的指令碼(子shell)中定義的變數資訊可以被父指令碼引用,“ParentShell.sh : Get: $SUB_VAR=Sub”中等號右邊內容為Sub,表示引用了子shell指令碼中的變數,此項與fork模式不同。
對比exec模式與source模式的區別
對於exec模式,有以下結論:
- 父指令碼執行後的PID資訊與巢狀指令碼(子shell指令碼)執行後的PID資訊一致,說明exec模式呼叫指令碼同不會產生子shell,而是在同一個shell裡執行,此項與source模式相同
- 父指令碼的變數資訊會被嵌入的指令碼(子shell指令碼)引用,“SubShell.sh get$ParentVar=Parent”中等號右邊的Parent即為呼叫父指令碼變數後輸出的結果,此項與fork及source模式都相同。
- 利用exec模式執行嵌入指令碼(子shell指令碼)的問題是,在執行完嵌入指令碼後,緊接著嵌入指令碼後的所有父指令碼命令將不再執行,而是直接退出父指令碼,此項與fork及source模式都不同。
shell呼叫指令碼3種不同模式的應用場景
- fork模式呼叫指令碼的應用場景 fork模式呼叫指令碼主要應用於常規巢狀指令碼執行的場合,巢狀的指令碼只是執行相應的命令操作,不會生成相應的程序資訊,父指令碼不需要引用巢狀的指令碼內的變數及函式等資訊,其次在巢狀指令碼中定義的變數及函式等不會影響到父指令碼中相同的資訊定義。
- exec模式呼叫指令碼的應用場景 exec模式呼叫指令碼需要應用於巢狀指令碼在主指令碼的末尾執行的場合,因此,此種模式的應用並不多見,並且可以被source模式完全取代。
- source模式呼叫指令碼的應用場景 source模式呼叫指令碼是比較重要且最常用的一種巢狀方式,主要應用之一是執行巢狀指令碼啟動某些服務程式。例如:在利用巢狀指令碼啟動Tomcat程式並生成PID程式檔案時,如果選擇fork模式,那麼生成的PID檔案資訊就和執行“ps -ef”命令輸出的PID資訊不一致,這將會導致執行kill `cat tomcat_pid` 命令時,不能正確關閉Tomcat程式,而選擇source模式就可以解決此問題。 source模式呼叫指令碼的另外一個應用就是使得巢狀指令碼中的變數及函式等資訊被父指令碼使用,從而實現更多的業務處理。