在第二周课程中,老师说到使用小括号(list)可以调出子shell,并举了以下例子:
例
这个例子很好理解:在当前shell,变量name被赋值为wang;而在通过()进入的子shell里,变量name被赋值为zhang。所以第一个echo显示当前shell内变量name的值为wang;第二个echo显示子shell内变量name的值为zhang;第三个echo是子shell结束后,回到当前shell,变量name的值为wang.
紧接着,老师又写了一行命令,让大家判断它的输出值会是什么:
例
根据之前的经验,name作为当前shell的一个普通变量,并没有被export,子shell是不会继承的,输出值自然是“wang 空值 wang”咯。然后结果却出乎意料:
例
怎么回事,在没有export的情况下,子shell怎么继承了父shell的环境变量?
课后特意去询问老师,老师大致解释说,用()打开的子shell与执行bash打开的子shell还是不同的,它没有单独加载一些shell的配置,会继承父shell的内容等等。本着先记住,以后慢慢理解的原则,我也就似懂非懂地决定先硬记住了。
但是两种子shell到底有什么区别呢?为什么一个能继承父shell的环境,另一个却不能呢?
今天无意中看到一篇帖子,里面解释了SHLVL和BASH_SUBSHELL两个变量,让这个疑惑迎刃而解。
首先,借用一下大神对这两个变量的定义:
SHLVL是记录多个bash进程实例嵌套深度的累加器
BASH_SUBSHELL是记录一个bash进程实例的多层subshell嵌套深度的累加器
如果概念比较不好理解,让我们通过例子来理解吧
1
2
图1中,SHLVL值随着bash命令执行的次数增加,而BASH_SUBSHELL值不变。本质上说,这是通过执行外部命令bash开启了新进程(而这个新进程恰好也是shell进程罢了),而SHLVL记录了这种嵌套的shell进程实例的层数。这样的child shell进程是通过执行硬盘上的命令来生成的,它是独立的(会读取加载/etc/profile.d/*.sh /etc/bashrc ~/.bashrc等bash配置文件),只能访问所谓的"父"shell的环境变量。
而通过()开启的叫做subshell的才是当前shell名副其实的子shell,它可以访问父shell的任何变量。图2可以看到通过()开启的子shell的嵌套层级数。注意,SHLVL的值是不变的,也就是没有开启新的child shell。
我们来看看进程树:
-
bash
SHLVL
-
() 因为执行完小括号内的命令后,subshell进程会立马结束,所以需要通过sleep命令来留住subshell进程,进而通过pstree -p命令观察它
()
() pstree
从进程树上看,child shell和subshell他们都在当前shell进程(pid15036)后.不同点在于,child shell的$BASHPID和$$值相同,subshell则没有自己独立$$值,只有自己的$BASHPID.
翻译成中文的时候笼统地管它们叫子shell。然而它们是有本质上的区别的。现在你了解了吗?
补充:
An example of the difference between a subshell and a child process that happens to be a shell:
例
补充2:
通过管道,也能打开subshell
例
网友评论