Shell编程-13-子Shell和Shell嵌套

什么是子Shell

    子Shell的概念其实是贯穿整个Shell的,如果想要更好的理解和写Shell脚本则必须要了解子Shell的相关知识。其概念如下所示:

子Shell本质就是从当前的Shell环境中打开一个新的Shell环境,而新开的Shell称之为子Shell(SubShell),相应的开启子Shell的环境称之为父Shell。子Shell和父Shell是子进程和父进程的关系,而这个进程则全部是bash进程。子Shell可以从父Shell中继承变量、命令全路径、文件描述符、当前工作目录等。在子Shell中常用的两个变量如下所示:

  • $BASH_SUBSHELL:查看从当前进程开始的子Shell层数
  • $BASHPID:查看当前所处BASH的PID
在Linux系统中,系统运行的程序基本都是从CentOS 6.x(init)或CentOS7.x(systemd)PID为1的进程)继承而来的,所有的程序都可以看作为init的子进程。
# CentOS 6.x
[root@localhost data]# pstree -hp
init(1)─┬─NetworkManager(3643)
        ├─Xvnc(22811)
        ├─abrtd(4760)
        ├─acpid(3755)
        ├─atd(4801)
        ├─auditd(3392)───{auditd}(3393)
        ├─automount(3849)─┬─{automount}(3850)
        │                 ├─{automount}(3851)
        │                 ├─{automount}(3854)
        │                 └─{automount}(3857)
# CentOS 7.x
[root@localhost ~]# pstree -hp
systemd(1)─┬─ModemManager(1051)─┬─{ModemManager}(1068)
           │                    └─{ModemManager}(1076)
           ├─Xvnc(5563)─┬─{Xvnc}(5566)
           │            ├─{Xvnc}(5567)
           │            ├─{Xvnc}(5568)


子Shell产生的途径

通过后台作业:&

[root@localhost Test]# cat jobs.sh
#!/bin/bash
parentShell="ParentShell"
echo "Parent Shell start and Level:"$BASH_SUBSHELL
# define subshell
{
 echo "SubShell start and Level:"$BASH_SUBSHELL
 subShell="SubShell"
 echo "SubShell value: ${subShell}"
 echo "parentShell value: ${parentShell}"
# sleep 5
 echo "SubShell end and Level: $BASH_SUBSHELL "
} & # running in backgroud
echo "Parent end and Level:$BASH_SUBSHELL"

if [ -z "${subShell}" ]
  then
    echo "subShell is not defined in ParentShell"
else
  echo "subShell is defined in ParentShel"
fi
[root@localhost Test]# bash jobs.sh
Parent Shell start and Level:0
Parent end and Level:0
subShell is not defined in ParentShell
SubShell start and Level:1
SubShell value: SubShell
parentShell value: ParentShell
SubShell end and Level: 1

根据运行结果,结论如下所示:

  • 在Shell中可以使用&产生子Shell
  • &产生的子Shell可以直接引用父Shell的变量,而子Shell产生的变量不能被父Shell引用
  • 在Shell中使用&可以实现多线程并发

通过管道:|

[root@localhost Test]# cat jobs.sh
#!/bin/bash
parentShell="ParentShell"
echo "Parent Shell start and Level:"$BASH_SUBSHELL
# define subshell
echo "" |  # 管道
{
 echo "SubShell start and Level:"$BASH_SUBSHELL
 subShell="SubShell"
 echo "SubShell value: ${subShell}"
 echo "parentShell value: ${parentShell}"
# sleep 5
 echo "SubShell end and Level: $BASH_SUBSHELL "
}
echo "Parent end and Level:$BASH_SUBSHELL"

if [ -z "${subShell}" ]
  then
    echo "subShell is not defined in ParentShell"
else
  echo "subShell is defined in ParentShel"
fi
[root@localhost Test]# bash jobs.sh
Parent Shell start and Level:0
SubShell start and Level:1
SubShell value: SubShell
parentShell value: ParentShell
SubShell end and Level: 1
Parent end and Level:0
subShell is not defined in ParentShell

根据运行结果,结论如下所示:

  • 在Shell中可以使用管道产生子Shell
  • 管道产生的子Shell可以直接引用父Shell的变量,而子Shell产生的变量不能被父Shell引用
  • 管道产生的Shell是顺序执行的,仅能在子Shell执行完成后才能返回父Shell中继续执行,这一点也是与&最大的区别。

通过()

[root@localhost Test]# cat jobs.sh
#!/bin/bash
parentShell="ParentShell"
echo "Parent Shell start and Level:"$BASH_SUBSHELL
# define subshell
(
 echo "SubShell start and Level:"$BASH_SUBSHELL
 subShell="SubShell"
 echo "SubShell value: ${subShell}"
 echo "parentShell value: ${parentShell}"
# sleep 5
 echo "SubShell end and Level: $BASH_SUBSHELL "
)
echo "Parent end and Level:$BASH_SUBSHELL"

if [ -z "${subShell}" ]
  then
    echo "subShell is not defined in ParentShell"
else
  echo "subShell is defined in ParentShel"
fi
[root@localhost Test]# bash jobs.sh
Parent Shell start and Level:0
SubShell start and Level:1
SubShell value: SubShell
parentShell value: ParentShell
SubShell end and Level: 1
Parent end and Level:0
subShell is not defined in ParentShell

根据运行结果,结论如下所示:

  • 在Shell中可以使用()产生子Shell
  • ()产生的子Shell可以直接引用父Shell的变量,而子Shell产生的变量不能被父Shell引用
  • ()产生的Shell是顺序执行的,仅能在子Shell执行完成后才能返回父Shell中继续执行,

看到这个结果,大家会不会觉得使用()跟使用管道一样的?

通过调用外部Shell

[root@localhost Test]# cat subShell.sh parentShell.sh  -n
       # SubShell
     1  #!/bin/bash
     2   echo "SubShell start and Level:"$BASH_SUBSHELL
     3   subShell="SubShell"
     4   echo "SubShell value: ${subShell}"
     5   echo "parentShell value: ${parentShell}"
     6   echo "parentExportShell value: ${parentExportShell}"
     7   if [ -z "${parentShell}"  ];then
     8      echo "parentShell value is : null"
     9   else
    10      echo "parentShell value is : "${parentShell}
    11   fi
    12
    13  # ParentShell
    14  #!/bin/bash
    15  parentShell="Parent"
    16  export parentExportShell="parentExportShell"
    17  echo "Parent Shell start and Level:"$BASH_SUBSHELL
    18  bash ./subShell.sh # invoke subshell
    19  sleep 3
    20  echo "Parent Shell end and Level:"$BASH_SUBSHELL
    21  if [ -z "${subShell}" ]
    22    then
    23     echo "subShell is not defined in ParentShell"
    24  else
    25     echo "subShell is defined in ParentShell"
    26  fi
[root@localhost Test]# bash parentShell.sh
Parent Shell start and Level:0
SubShell start and Level:0
SubShell value: SubShell
parentShell value:
parentExportShell value: parentExportShell
parentShell value is : null
Parent Shell end and Level:0
subShell is not defined in ParentShell

根据运行结果,结论如下所示:

  • 在Shell中可以通过外部Shell脚本产生子Shell
  • 在调用外部Shell时,父Shell定义的变量不能被子Shell继承,如果要继承父Shell的变量,必须使用export使其成为全局环境变量。
  • 调用外部Shell产生的Shell是顺序执行的,仅能在子Shell执行完成后才能返回父Shell中继续执行,

Shell脚本调用模式

    通常在大型的项目中,都会将较大模块进行拆分为多个小模块进行代码编写调试等。因此在一个Shell脚本中也不可能包含所有模块,一般都采用在一个脚本中去调用当前用到的脚本,这种被称之为Shell嵌套。在一个脚本中嵌套脚本的方式主要有forkexecsource

fork模式调用脚本

    fork模式是最普通的脚本调用方式。在使用该方式调用脚本时,系统会创建一个子Shell去调用脚本。其调用方式如下所示:

/bin/bash /path/shellscript.sh # 未给脚本添加执行权限时
或
/path/shellscript.sh # 脚本拥有执行权限时

fork本质是复制进程。使用该方式时,fork会复制当前进程做为一个副本,而后将这些资源交给子进程。因此子进程会继承父进程的一些资源,如环境变量、变量等。而父进程却是完全独立的,子进程和父进程相当于面向对象中一个对象的两个实例。

exec模式调用脚本

    exec调用脚本时,不会开启一个新的子Shell来进行调用脚本,被调用的脚本和调用脚本在同一个Shell内执行。但需要注意的是使用exec调用新脚本后,在执行完新脚本的内容后,不再返回到调用脚本中执行后续未执行的内容,这也是与fork调用脚本的主要区别。其主要调用方式如下所示:

exec /path/shellscript.sh

exec的本质是加载另外一个程序来代替当前运行的进程。即在不创建新进程的情况下去加载一个新程序,而在进程执行完成后就直接退出exec所在的Shell环境。

source模式调用脚本

    source调用脚本时,也不会开启一个新的子Shell来执行被调用的脚本,同样也是在同一个Shell中执行,因此被调用脚本是可以继承调用脚本的变量、环境变量等。与exec调用方式的区别是,source在执行完被调用脚本的内容后,依然会返回调用脚本中,去执行调用脚本中未执行的内容。其主要调用方式如下所示:

source /path/shellscript.sh
或
. /path/shellscript.sh  # .和source是等价的

三种调用模式示例

    示例代码如下所示:

[root@localhost Test]# cat -n subShell.sh parentShell.sh
     1  #!/bin/bash
     2   echo "SubShell start and Level:"$BASH_SUBSHELL
     3   echo "SubShell PID is:" $$
     4   subShell="SubShell"
     5   echo "SubShell value: ${subShell}"
     6   echo "parentShell value: ${parentShell}"
     7   echo "parentExportShell value: ${parentExportShell}"
     8   if [ -z "${parentShell}"  ];then
     9      echo "parentShell value is : null"
    10   else
    11      echo "parentShell value is : "${parentShell}
    12   fi
    13  #!/bin/bash
    14  # print usage
    15  function Usage() {
    16    echo "Usage:$0 {fork|exec|source}"
    17    exit 1
    18  }
    19  # print return variable
    20  function PrintPara() {
    21   if [ -z "${subShell}" ]
    22    then
    23     echo "subShell is not defined in ParentShell"
    24   else
    25     echo "subShell is defined in ParentShell "${subShell}
    26   fi
    27  }
    28  # invoke pattern
    29  function ParentFunction() {
    30    parentShell="Parent"
    31    export parentExportShell="parentExportShell"
    32    echo "Parent Shell start and Level:"$BASH_SUBSHELL
    33    echo "Parent PID is:"$$
    34    case "$1" in
    35      fork)
    36         echo "Using fork pattern"
    37         /bin/bash ./subShell.sh
    38         PrintPara ;;
    39      exec)
    40         echo "Using exec pattern"
    41         exec ./subShell.sh
    42         PrintPara ;;
    43      source)
    44         echo "Using source pattern"
    45         source ./subShell.sh
    46         PrintPara ;;
    47      *)
    48        echo "Input error ,usage is:" Usage
    49     esac
    50  }
    51  # check parameter number
    52  function CheckInputPara() {
    53    if [ $# -ne 1 ]
    54      then
    55        Usage
    56    fi
    57    ParentFunction $*
    58  }
    59  CheckInputPara $*

1、fork调用结果:

[root@localhost Test]# bash parentShell.sh fork
Parent Shell start and Level:0
Parent PID is:26413
Using fork pattern
SubShell start and Level:0
SubShell PID is: 26414
SubShell value: SubShell
parentShell value:
parentExportShell value: parentExportShell
parentShell value is : null
subShell is not defined in ParentShell

1、父Shell和子Shell的PID不一样,则可以说明产生了新的子进程
2、调用脚本中定义的全局变量可以传入到被调用脚本,而被调用的脚本中定义的变量是无法返回到调用脚本中的

2、exec调用结果:

[root@localhost Test]# chmod +x subShell.sh
[root@localhost Test]# bash parentShell.sh exec
Parent Shell start and Level:0
Parent PID is:25543
Using exec pattern
SubShell start and Level:0
SubShell PID is: 25543
SubShell value: SubShell
parentShell value:
parentExportShell value: parentExportShell
parentShell value is : null

1、父Shell和子Shell的PID一样,则可以说明未产生新的子进程
2、调用脚本中定义的全局变量可以传入到被调用脚本
3、最重要的一点就是在执行完被调用脚本后直接退出Shell了,而调用脚本未执行的内容并没有被执行

3、source调用结果:

[root@localhost Test]# bash parentShell.sh source
Parent Shell start and Level:0
Parent PID is:19955
Using source pattern
SubShell start and Level:0
SubShell PID is: 19955
SubShell value: SubShell
parentShell value: Parent
parentExportShell value: parentExportShell
parentShell value is : Parent
subShell is defined in ParentShell: SubShell

1、父Shell和子Shell的PID一样,则可以说明未产生新的子进程
2、调用脚本中定义的普通变量和全局变量可以传入到被调用脚本,反之亦然
3、最重要的一点就是在执行完被调用脚本后返回调用脚本中继续执行剩下的内容

三种调用模式使用场景

  • 1、fork模式使用场景

fork模式常用于常规嵌套脚本执行的场景中,仅仅是执行嵌套脚本中命令,调用脚本也不需要使用被调用脚本中的变量和函数等信息。

  • 2、exec模式使用场景

exec模式常用于调用脚本中末尾中。而这种模式在执行完被调用脚本中就直接退出,因此使用较少,可使用source代替exec

  • 3、source模式使用场景

source模式算是在Shell常用的一种脚本嵌套调用模式,常用于执行嵌套脚本启动一些服务程序等,其最大的优点就是嵌套脚本中定义的变量和函数等资源可以被调用脚本获取和使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343