實例解析shell子進程(subshell )shell
經過實例,解析我的對shell子進程的一個瞭解,主要包括如下幾個方面bash
1:什麼是shell子進程ssh
2:shell什麼狀況下會產生子進程async
3:子進程的特色與注意事項ide
4:$變量$$在腳本里的意義,及如何獲得子進程裏的進程號函數
參考文檔:apue,bash的man和info文檔測試
1:什麼是shell子進程ui
子進程,是從父子進程的概念出發的,unix操做系統的進程從init進程開始(init進程爲1,而進程號0爲系統原始進程,如下討論的進程原則上不包括進程0)均有其對應的子進程,就算是因爲父進程先行結束致使的孤兒進程,也會被init領養,使其父進程ID爲1。spa
也由於全部的進程均有父進程,事實上,全部進程的建立,均可視爲子進程建立過程。在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 subshell
3:括號命令列表
()操做符
Placing a list of commands between parentheses causes a subshell
environment to be created
4:執行外部腳本、程序:
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 echo
echo 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 ARGUMENTS
bash 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 $sum
0
[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 shell
2:子進程裏的$$對應的是哪一個 current shell
3:如何獵取子進程的$$?
作爲調試和測試,下邊的例子引用幾個變量,
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 1
bash(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 20
sshd(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 19718
19718
+[/bin/bash][0][19718-19719][4]["./subshell2.sh"][][0]
+sleep 10
bash,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給顯示了出來。