linux shell脚本嵌套,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嵌套。在一个脚本中嵌套脚本的方式主要有fork、exec和source。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值