LINUX学习:进入子shell的各种情况分析

LINUX学习:进入子shell的各种情况分析,第1张

概述介绍《LINUX学习:进入子shell的各种情况分析》开发教程,希望对您有用。

《liNUX学习:进入子shell的各种情况分析》要点:
本文介绍了liNUX学习:进入子shell的各种情况分析,希望对您有用。如果有疑问,可以联系我们。

子shell的概念贯穿整个shell,写shell脚本时更是不可不知.所谓子shell,即从当前shell环境新开一个shell环境,这个新开的shell环境就称为子shell(subshell),而开启子shell的环境称为该子shell的父shell.子shell和父shell的关系其实便是子进程和父进程的关系,只不过子shell和父shell是关联的进程是bash进程.

子shell会从父shell中继承很多环境,如变量、命令全路径、文件描述符、当前工作目录、陷阱等等,但子shell有很多种类型,分歧类型的子shell继承的环境不相同.可以使用$BASH_SUBSHELL变量来查看从当前进程开始的子shell层数,$BASHPID查看当前所处BASH的PID,这分歧于特殊变量"$$"值,因为"$$"会从父进程继承.

何时产生子shell

要解释清楚子shell以及产生何种类型的子shell,必要搞清楚linux中如何产生子进程.linux上创建子进程的方式有三种:一种是fork出来的进程,一种是exec出来的进程,一种是clone出来的进程.此处无需关心clone,因为它用来实现linux中的线程.

(1).fork是复制进程,它会复制当前进程的副本(不考虑写时复制的模式),以适当的方式将这些资源交给子进程.所以子进程掌握的资源和父进程是一样的,包含内存中的内容,所以也包含环境变量和变量.但父子进程是完全独立的,它们是一个程序的两个实例.

(2).exec是加载另一个应用程序,替代当前运行的进程,也便是说在不创建新进程的情况下加载一个新程序.exec还有一个动作:在进程执行完毕后,退出exec所在的shell环境.

所以为了保证进程平安,若要形成新的且独立的子进程,都会先fork一份当前进程,然后在fork出来的子进程上调用exec来加载新程序替代该子进程.例如在bash下执行cp命令,会先fork出一个bash,然后再exec加载cp程序覆盖子bash进程变成cp进程.

再来说明子shell的问题.一般fork出来的子进程,内容和父进程是一样的(包含变量),例如执行cp命令时也能获取到父进程的变量.但是cp命令在哪里执行呢?执行cp命令敲入回车后,当前的bash进程fork出一个子bash,然后子bash通过exec加载cp程序替代子bash.这算是进入了子shell吗?更通用的问题是:什么情况下会进入子shell环境,什么时候不进入子shel环境呢?

判断是否进入了子shell的方式非常简单,执行"echo $BASHPID",如果该值和父bash进程的pID值不同,则表现进入了子shell.在shell中是否进入子shell的情况可以分为几种:

①.执行bash内置敕令时.

bash内置命令是非常特殊的,父进程不会创立子进程来执行这些命令,而是直接在当前bash环境中执行.但如果将内置命令放在管道后,则此内置命令将和管道左边的进程同属于一个进程组,所以仍然会创立子shell.

[root@linuxIDc ~]# echo $BASHPID   # 当前BASHPID65230[root@linuxIDc ~]# let a=$BASHPID   # bash内置命令,不进入子shell[root@linuxIDc ~]# echo $a65230
[root@linuxIDc ~]# echo $BASHPID65230[root@linuxIDc ~]# cd | expr $BASHPID      # 管道使得任何命令都进入进程组,会进入子shell   65603

②.执行bash命令自己时.

这是一个很巧合的命令.bash命令本身是bash内置命令,在当前shell环境下执行内置命令本不会创建子shell,也就是说不会有独立的bash进程出现,而实际结果则表现为新的bash是一个子进程.其中一个原因是执行bash命令会加载各种环境配置项,为了父bash的环境得到掩护而不被覆盖,所以应该让其以子shell的方式存在.虽然fork出来的bash子进程内容完全继承父shell,但因重新加载了环境配置项,所以子shell没有继承普通变量,更准确的说是覆盖了从父shell中继承的变量.不妨试试在/etc/bashrc文件中定义一个变量,再在父shell中export名称相同值却不同的环境变量,然后到子shell中看看该变量的值为何?

[root@linuxIDc ~]# echo "var=55" >>/etc/bashrc[root@linuxIDc ~]# export var=66[root@linuxIDc ~]# bash[root@linuxIDc ~]# echo $var55

由成果55可知,执行bash时加载的/etc/bashrc中的变量覆盖了父bash中的导出的环境变量值66.

其实执行bash命令,既可以认为进入了子shell,也可以认为没有进入子shell.从bash是内置命令的角度来考虑,它不会进入子shell,这一点在执行bash命令后从变量$BASH_SUBSHELL的值为0可以验证出来.但从执行bash命令后进入了新的shell环境来看,它有其父bash进程,且$BASHPID值和父shell分歧,所以它算是进入了子shell.

[root@linuxIDc ~]# echo $BASHPID65230[root@linuxIDc ~]# bash[root@linuxIDc ~]# echo $BASHPID65534

 

③.执行shell剧本时.

脚本中第一行总是"#!/bin/bash"或者直接"bash xyz.sh",这和上面的执行bash进入子shell其实是一回事,都是使用bash命令进入子shell.只不过此时的bash命令和情况②中直接执行bash命令所隐含的选项不一样,所以继承和加载的shell环境也不一样.事实也确实如此,它仅只继承父shell的某些环境变量,别的环境一概初始化.

另外,执行shell脚原形比于直接执行bash命令,还多了一个动作:脚本执行完毕后自动退出子shell.

[root@linuxIDc ~]# cat b.sh #!/bin/bashecho $BASHPID[root@linuxIDc ~]# echo $BASHPID65534[root@linuxIDc ~]# ./b.sh 65570

 

④.执行shell函数时.

其实shell函数便是命令,它和bash内置命令的情况一样.直接执行时不会进入子shell,但放在管道后会进入子shell.

[root@linuxIDc ~]# fun_test (){ echo $BASHPID; }   # 定义一个函数,输出BASHPID变量的值[root@linuxIDc ~]# echo $BASHPID 65230[root@linuxIDc ~]# fun_test      # 阐明执行函数不会进入子shell65230[root@linuxIDc ~]# cd | fun_test   # 但放在管道后会进入子shell65605

⑤.执行非bash内置命令时.

例如执行cp命令、grep命令等,它们直接fork一份bash进程,然后使用exec加载程序替代该子bash.此类子进程会继承所有父bash的环境.但严格地说,这已经不是子shell,因为exec加载的程序已经把子bash进程替换掉了,这意味着丢失了很多bash环境.在bash文档中,直接称谓这种环境为"单独的环境",和子shell的概念类似.

[root@linuxIDc ~]# let a=$BASHPID   # let是内置命令[root@linuxIDc ~]# echo $a65230[root@linuxIDc ~]# echo $BASHPID    # echo是非内置命令,成果是不进入子shell65230

 

⑥.敕令替换.

当命令行中包括了命令替换部分时,将开启一个子shell先执行这部分内容,再将执行结果返回给当前命令.因为这次的子shell不是通过bash命令进入的子shell,所以它会继承父shell的所有变量内容.这也就解释了"echo $(echo $$)"中"$$"的结果是当前bash的pID号,而不是子shell的pID号,但"echo $(echo $BASHPID)"却和父bash进程的pID不同,因为它不是使用bash命令进入的子shell.

[root@linuxIDc ~]# echo $BASHPID65230[root@linuxIDc ~]# echo $(echo $BASHPID)      # 使用敕令替换$()进入子shell65612

⑦.使用括号()组合一系列敕令.

例如(ls;date;echo haha),自力的括号将会开启一个子shell来执行括号内的命令.这种情况等同于情况⑤.

[root@linuxIDc ~]# echo $BASHPID65230[root@linuxIDc ~]# (echo $BASHPID)  # 使用括号()的敕令组合进入子shell65613

⑧.放入后台运行的任务.

它不仅是一个自力的子进程,还是在子shell环境中运行的.例如"echo hahha &".

[root@linuxIDc ~]# echo $BASHPID65230[root@linuxIDc ~]# echo $BASHPID &   # 放入后台运行的任务进入子shell[1] 65614[root@linuxIDc ~]# 65614[1]+  Done                    echo $BASHPID 

⑨.过程替换.

既然是新过程了,当然进入子shell执行.例如"cat <(echo haha)".

[root@linuxIDc ~]# echo $BASHPID65230[root@linuxIDc ~]# cat <(echo $BASHPID)    # 进程替换"<()"进入子shell65616

必要说明的是,子shell的环境设置不会粘滞到父shell环境,也就是说子shell的变量等不会影响父shell.

最后,建议同时阅读另一篇文章:bash启动时情况配置流程,此文中详细解释了bash启动时加载哪些配置文件.

本文永远更新链接地址

更多liNUX教程,尽在内存溢出PHP学院专栏。欢迎交流《liNUX学习:进入子shell的各种情况分析》!

总结

以上是内存溢出为你收集整理的LINUX学习:进入子shell的各种情况分析全部内容,希望文章能够帮你解决LINUX学习:进入子shell的各种情况分析所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://www.outofmemory.cn/yw/1042207.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-24
下一篇 2022-05-24

发表评论

登录后才能评论

评论列表(0条)

保存