实例解析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给显示了出来。

相关文章
相关标签/搜索