執行個體解析shell子進程(subshell) 通過執行個體,解析個人對shell子進程的一個瞭解,主要包括以下幾個方面1:什麼是shell子進程2:shell什麼情況下會產生子進程3:子進程的特點與注意事項4:$變數$$在指令碼裡的意義,及如何得到子進程裡的進程號 參考文檔:apue,bash的man和info文檔 1:什麼是shell子進程 子進程,是從父子進程的概念出發的,unix作業系統的進程從init進程開始(init進程為1,而進程號0為系統原始進程,以下討論的進程原則上不包括進程0)均有其對應的子進程,就算是由於父進程先行結束導致的孤兒進程,也會被init領養,使其父進程ID為1。 也因為所有的進程均有父進程,事實上,所有進程的建立,都可視為子進程建立過程。在apue一書裡提及unix作業系統進程的建立,大抵上的模式都是進行fork+exec類系統調用。 理解子進程的建立執行,需要至少細分到二個步驟,包括通過fork建立子進程環境,通過exec載入並執行進程代碼。其間諸如繼承的環境變數等細節,可以查看apue第八章相關章節。 而shell子進程(以下均稱subshell),顧名思義,就是由“當前shell進程”建立的一個子進程 2:shell什麼情況下會產生子進程 以下幾個建立子進程的情況。(以下英文摘自info bash)1:&,提交後台作業If a command is terminated by the control operator `&', the shell executes the command asynchronously in a subshell.2:管道Each command in a pipeline is executed in its own subshell3:括弧命令列表()操作符 Placing a list of commands between parentheses causes a subshell environment to be created4:執行外部指令碼、程式:When Bash finds such a file while searching the `$PATH' for a command, it spawns a subshell to execute it. In other words, executing filename ARGUMENTS is equivalent to executing bash filename ARGUMENTS 說明:大致上子進程的建立包括以上四種情況了。需要說明的是只要是符合上邊四種情況之一,便會建立(fork)子進程,不因是否是函數,命令,或程式,也不會因為是內建函數(buitin)或是外部程式。 此外,上邊提到子進程建立與執行的二個步驟,shell子進程的建立在步驟之一併無多大差別,一般還是父進程調用fork產生進程環境,估在第二步exec的時候,是存在差別的。 shell做為解釋語言程式,提供給第二步exec載入和執行的程式體並不是指令碼本身,而是由第一行#!指定的,預設為shell程式,當然也可以是awk,sed等程式,在之前寫過的一篇文章裡:shell指令碼的set id如何生效就有提及。這裡不再展開討論。 只不過子進程的執行會根據情況而有所差別,對於內建函數,exec程式體為shell程式,並在會在子shell直接調用內建函數, 而外部函數或程式,在建立了子進程環境後,大致會有二種執行情況:1:直接exec外部程式,比如下邊例子中直接執行的sleep,pstree命令等2:subshellexec程式體為shell程式,在此基礎上會進一步建立一個子進程以執行函數。比如下邊例子中通過函數提交背景程式中的shell命令等 例:內建函數(直接在subshell裡執行,不管是否通過函數)[root@localhost shell]# mkfifo a[root@localhost shell]# type echoecho is a shell builtin[root@localhost shell]# b(){ echo a>a; }[root@localhost shell]# b &[1] 15697[root@localhost shell]# echo a>a &[2] 15732[root@localhost shell]# pstree -pa $$bash,571 |-bash,15697 |-bash,15732 `-pstree,15734 -pa 571 例:定義函數並提交後台進行(函數調用中的sleep在subshell之下又建立一個子進程,而pstree,sleep命令的直接執行,則是直接在子進程上進行) [root@localhost shell]# a(){ sleep 30; } ;[root@localhost shell]# sleep 40 &[1] 15649[root@localhost shell]# a &[2] 15650[root@localhost shell]# pstree -pa $$bash,571 |-bash,15650 | `-sleep,15651 30 |-pstree,15652 -pa 571 `-sleep,15649 40 對於第四點,要注意,shell指令碼的執行模式,在第四點的二種模式下,shell是會建立子進程的: filename ARGUMENTSbash filename ARGUMENTS 但shell同時提供二種不建立子程式的進程建立方式1:source命令,使用方法Source filename ARGUMENTS或. filename ARGUMENTS 此種方法,直接在當前shell進程中執行filename指令碼,filename結束後繼續返回當前shell進程 2:exec命令,使用方法Exec filename ARGUMENTS此種方法直接在當前shell進程中執行filname指令碼,filename結束後退出當前shell進程 3:子進程的特點與注意事項這方面不具體展開,只提一點寫指令碼容易出現的錯誤。 做為子進程,其進程環境與父進程的環境是獨立的, 所以在變數傳遞過程中,需要注意子進程內部不能更改到父進程的變數。 比如如下通過管道求和並賦給外部變數sum例子,結果sum值並不會因此改變:[root@localhost shell]# sum=0[root@localhost shell]# echo '1 2 3 4' |sed 's/ //n/g'|while read line; do sum+=$line; done[root@localhost shell]# echo $sum0[root@localhost shell]# 4:變數$$在指令碼裡的意義 變數$$代表的是當前shell進程的進和id,這裡要特別留意“當前shell”,看看info bash裡的說明`$' Expands to the process ID of the shell. In a `()' subshell, it expands to the process ID of the invoking shell, not the subshell.再看看man bash裡的說明 $ Expands to the process ID of the shell. In a () subshell, it expands to the process ID of the current shell, not the subshell. 所以在實際環境中,$$並不一定“當前進程”的進程號,而是當前shell進程的進程號。從文檔中,需要留意的便是 invoking shell (info) 或 current shell(man) 與 當前subshell進程的關係了 這就引出了幾個問題1:到底怎麼樣算是 current shell2:子進程裡的$$對應的是哪個 current shell3:如何獵取子進程的$$? 做為調試和測試,下邊的例子引用幾個變數, BASH_SOURCE' An array variable whose members are the source filenames corresponding to the elements in the `FUNCNAME' array variable.`BASH_LINENO' An array variable whose members are the line numbers in source files corresponding to each member of FUNCNAME. `${BASH_LINENO[$i]}' is the line number in the source file where `${FUNCNAME[$i]}' was called. The corresponding source file name is `${BASH_SOURCE[$i]}'. Use `LINENO' to obtain the current line number.`FUNCNAME' An array variable containing the names of all shell functions currently in the execution call stack. The element with index 0 is the name of any currently-executing shell function. The bottom-most element is "main". This variable exists only when a shell function is executing. Assignments to `FUNCNAME' have no effect and return an error status. If `FUNCNAME' is unset, it loses its special properties, even if it is subsequently reset. 指令碼裡set -x,並設定PS4跟蹤程式執行過程 PS4='+[$SHELL][$BASH_SUBSHELL][$PPID-$$][$LINENO]["${BASH_SOURCE[*]}"][${FUNCNAME[*]}][${BASH_LINENO[*]}]/n + PS4設定顯示值如下:[$SHELL]:當前shell路徑[$BASH_SUBSHELL]:子shell路徑長度[$PPID-$$]:父進程id,和變數$$值(current shell進程ID)[$LINENO]:在當前shell的命令列號["${BASH_SOURCE[*]}"]:源指令碼程式檔案記錄隊列[${FUNCNAME[*]}]:函數調用記錄隊列[${BASH_LINENO[*]}]:執行行號記錄隊列 程式如下:[root@localhost shell]# cat -n subshell.sh+[/bin/bash][0][569-571][1060][""][][] +cat -n subshell.sh 1 #!/bin/bash 2 3 set -x 4 sub2() { 5 # sh subshell2.sh 6 sleep 1 7 } 8 sub() { 9 sub2 & 10 sleep 20 11 } 12 sub & 13 pstree -p $PPID 執行結果如下: [root@localhost shell]# bash subshell.sh+[/bin/bash][0][569-571][1059][""][][] +bash subshell.sh+[/bin/bash][0][571-17858][12]["subshell.sh"][][0] +sub+[/bin/bash][0][571-17858][13]["subshell.sh"][][0] +pstree -p 571+[/bin/bash][1][571-17858][10]["subshell.sh subshell.sh"][sub main][12 0] +sleep 20+[/bin/bash][1][571-17858][9]["subshell.sh subshell.sh"][sub main][12 0] +sub2+[/bin/bash][2][571-17858][6]["subshell.sh subshell.sh subshell.sh"][sub2 sub main][9 12 0] +sleep 1bash(571)---bash(17858)-+-bash(17859)-+-bash(17860)---sleep(17863) | `-sleep(17862) `-pstree(17861) 說明:1:首先在當前shell(進程id 571)下執行subshell.sh ,產生子進程,[$PPID-$$]=[571-17858]顯示此時執行subshell.sh指令碼的進程號為17858,[][0]顯示未進行函數調用,未產生函數記錄記錄說明在subshell.sh執行進程裡,$$值儲存的“current shell”即為本身,17858 [$LINENO]=[12]顯示在subshell.sh第12行調用sub函數sub函數在程式裡通過&提交後台方式調用,進程樹顯示,sub函數的調用在17858進程後建立子進程17859,執行體為bash此時,ppid指示父進程為571,$$變數值為17858,說明對於sub調用產生的進程,其“current shell”仍然為subshell.sh指令碼執行進程17858 [$LINENO]=[13]顯示在subshell.sh第13行執行pstree命令pstree命令調用方式是在指令碼裡直接調用進程樹顯示,pstree命令直接在17858進程後建立子進程17861並執行此時,ppid指示父進程為571,$$變數值為17858,說明對於這裡啟動並執行pstree命令的子進程,其“current shell”仍然為subshell.sh指令碼執行進程17858 2:[sub main][12 0]顯示進入sub函數內部 [$LINENO]=[9]顯示執行(在sub函數內)指令碼第9行,調用sub2函數進程樹顯示,sub2函數的調用在17859進程後建立子進程17860,執行體為bash此時,ppid仍然指示父進程為571,$$變數值為17858,說明對於sub2調用產生的進程,其“current shell”仍然為subshell.sh指令碼執行進程17858 [$LINENO]=[10]顯示執行(在sub函數內)指令碼第10行,sleep命令此處sleep命令調用方式是在指令碼裡的sub函數內直接調用進程樹顯示,sleep命令是sub函數調用時建立的進程17859後建立子進程17862並執行此時,ppid指示父進程為571,$$變數值為17858,說明對於這裡啟動並執行pstree命令的子進程,其“current shell”仍然為subshell.sh指令碼執行進程17858 3:[sub2 sub main][9 12 0]顯示進入sub2函數內部[6]顯示執行(在sub2函數內)指令碼第6行,sleep 1此處sleep命令調用方式是在指令碼裡的sub2函數內直接調用進程樹顯示,sleep命令是sub2函數調用時建立的進程17860後建立子進程17863並執行此時,ppid指示父進程為571,$$變數值為17858,說明對於這裡啟動並執行sleep命令的子進程,其“current shell”仍然為subshell.sh指令碼執行進程17858 終上這裡的$$只有二個值,一個是最初的bash shell: 571,一個是subshell.sh指令碼調用時產生的進程:17858其他由subshell.sh產生的子進程,無論是函數還是命令運行,$$變數值儲存的“current shell”均為subshell.sh調用時產生的進程:17858 由此推論出上邊提到的四種子shell的建立方法:提交後台,管道,括弧命令列表,指令碼調用。似乎只有第四種方法--指令碼調用--產生的subshell可以做o為“current shell” 可以通過以下二個例子再次論證這個推論例子一:變更指令碼調用方式,此種方式採用當前shell進程執行subshell.sh,不再建立一個子進程結果$$變數值儲存的“current shell”均為當前進程 [root@localhost shell]# source subshell.sh+[/bin/bash][0][569-571][1062][""][][] +source subshell.sh++[/bin/bash][0][569-571][3]["subshell.sh"][][1062] +set -x++[/bin/bash][0][569-571][13]["subshell.sh"][][1062] +pstree -p 569++[/bin/bash][0][569-571][12]["subshell.sh"][][1062] +sub++[/bin/bash][1][569-571][1]["subshell.sh subshell.sh"][sub source][12 1062] +sub2++[/bin/bash][2][569-571][2]["subshell.sh subshell.sh subshell.sh"][sub2 sub source][1 12 1062] sleep 1++[/bin/bash][1][569-571][2]["subshell.sh subshell.sh"][sub source][12 1062] +sleep 20sshd(569)---bash(571)-+-bash(18801)---sleep(18804) |-bash(18806)---bash(18808)---sleep(18809) `-pstree(18807) 例子二:在子進程裡邊,採用指令碼調用方式,或取子進程進程號為此增加一個指令碼subshell2.sh,並在subshell.sh進行調用 +cat -n subshell2.sh 1 #!/bin/bash 2 set -x 3 echo $PPID 4 sleep 10 +cat -n subshell.sh 1 #!/bin/bash 2 3 set -x 4 sub2() { 5 ./subshell2.sh & 6 sleep 1 7 } 8 sub() { 9 sub2 & 10 sleep 20 11 } 12 sub & 13 pstree -pa $PPID [root@localhost shell]# ./subshell.sh+[/bin/bash][0][569-571][1138][""][][] +./subshell.sh+[/bin/bash][0][571-19715][13]["./subshell.sh"][][0] +pstree -pa 571+[/bin/bash][0][571-19715][12]["./subshell.sh"][][0] +sub+[/bin/bash][1][571-19715][9]["./subshell.sh ./subshell.sh"][sub main][12 0] +sub2+[/bin/bash][2][571-19715][5]["./subshell.sh ./subshell.sh ./subshell.sh"][sub2 sub main][9 12 0] ./subshell2.sh+[/bin/bash][2][571-19715][6]["./subshell.sh ./subshell.sh ./subshell.sh"][sub2 sub main][9 12 0] sleep 1+[/bin/bash][1][571-19715][10]["./subshell.sh ./subshell.sh"][sub main][12 0] +sleep 20+[/bin/bash][0][19718-19719][3]["./subshell2.sh"][][0] +echo 1971819718+[/bin/bash][0][19718-19719][4]["./subshell2.sh"][][0] +sleep 10bash,571 `-subshell.sh,19715 ./subshell.sh |-pstree,19717 -pa 571 `-subshell.sh,19716 ./subshell.sh |-sleep,19721 20 `-subshell.sh,19718 ./subshell.sh |-sleep,19720 1 `-subshell2.sh,19719 ./subshell2.sh `-sleep,19722 10 從上邊的執行結果可以看出,當程式執行進入subshell2.sh時,建立了進程號19294的進程境,$$變數值儲存的“current shell”均為更新為subshell2.sh調用時建立的進程: 這樣,打出來的subshell2.sh的父進程id,實際上就是sub2調用時建立的進程,這樣就把一些$$本來顯示不出來的子進程id給顯示了出來。