进入子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 # 当前BASHPID 65230 [root@linuxidc ~]# let a=$BASHPID # bash内置命令,不进入子shell [root@linuxidc ~]# echo $a 65230

[root@linuxidc ~]# echo $BASHPID 65230 [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 $var 55

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

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

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/13540.html