實例解析shell子進程(subshell )

實例解析shell子進程(subshell )shell

 

經過實例,解析我的對shell子進程的一個瞭解,主要包括如下幾個方面bash

1:什麼是shell子進程ssh

2shell什麼狀況下會產生子進程async

3:子進程的特色與注意事項ide

4:$變量$$在腳本里的意義,及如何獲得子進程裏的進程號函數

 

參考文檔:apuebashmaninfo文檔測試

 

1:什麼是shell子進程ui

 

子進程,是從父子進程的概念出發的,unix操做系統的進程從init進程開始(init進程爲1,而進程號0爲系統原始進程,如下討論的進程原則上不包括進程0)均有其對應的子進程,就算是因爲父進程先行結束致使的孤兒進程,也會被init領養,使其父進程ID1spa

 

也由於全部的進程均有父進程,事實上,全部進程的建立,均可視爲子進程建立過程。在apue一書裏說起unix操做系統進程的建立,大抵上的模式都是進行fork+exec類系統調用。操作系統

 

理解子進程的建立執行,須要至少細分到二個步驟,包括

經過fork建立子進程環境,

經過exec加載並執行進程代碼。

其間諸如繼承的環境變量等細節,能夠查看apue第八章相關章節。

 

shell子進程(如下均稱subshell),顧名思義,就是由「當前shell進程」建立的一個子進程

 

 

 

2shell什麼狀況下會產生子進程

 

如下幾個建立子進程的狀況。(如下英文摘自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命令等

2subshellexec程序體爲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

 

例:定義函數並提交後臺進行

(函數調用中的sleepsubshell之下又建立一個子進程,

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同時提供二種不建立子程序的進程建立方式

1source命令,使用方法

Source   filename ARGUMENTS

.  filename ARGUMENTS

 

此種方法,直接在當前shell進程中執行filename腳本,filename結束後繼續返回當前shell進程

 

2exec命令,使用方法

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.sh12行調用sub函數

sub函數在程序裏經過&提交後臺方式調用,

進程樹顯示,sub函數的調用在17858進程後建立子進程17859,執行體爲bash

此時,ppid指示父進程爲571$$變量值爲17858

說明對於sub調用產生的進程,其「current shell」仍然爲subshell.sh腳本執行進程17858

 

[$LINENO]=[13]顯示在subshell.sh13行執行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給顯示了出來。

相關文章
相關標籤/搜索