转载

SHLVL 和 BASH_SUBSHELL 两个变量的区别

SHLVL 是记录多个 Bash 进程实例嵌套深度的累加器,而 BASH_SUBSHELL 是记录一个 Bash 进程实例中多个子 Shell(subshell)嵌套深度的累加器。

看不懂上面这句话不要紧,因为是我临时编的。其实如果你混淆了这两个变量,我猜你多半是对 BASH_SUBSHELL 这个变量名中的 subshell 概念不清,下面我们就讲讲什么是 subshell,什么不是。

很多人误以为在 Bash 里面再执行一次 Bash,或者再执行一个 Shell 脚本就是进入子 Shell 了,所以他们会有下面这样的疑问:

$ bash # 执行另外一个 bash 命令

$ echo $BASH_SUBSHELL

$ 0 # 怎么还是 0,我这不是在上个 Shell 的子 Shell 里吗?

$ echo ' echo $BASH_SUBSHELL ' > test; chmod +x test; ./test;

$ 0 # 难道再执行 Shell 脚本也不是子 Shell?

然而并不是,这些都不是子 Shell,这种情况只能描述成是“当前 Shell 启动了个外部命令,而这个外部命令刚好是个 Shell”,真正的子 Shell 是不需要重新执行硬盘上的外部命令的,全部是内存中的操作。上面这个示例中的 BASH_SUBSHELL 都应该替换成 SHLVL 才能看到累加效果。

有几本书给子 Shell 下过定义:

Advanced Bash-Scripting Guide 说:

A subshell is a child process launched by a shell (or shell script).

Bash Cookbook 说:

A subshell is a forked copy of the parent shell and shares it’s environment.

The Korn Shell: Unix & Linux Programming Manual 说:

A subshell is a separate copy of the parent shell, so variables, functions, and aliases from the parent shell are available to the subshell

第一本书流传较广,但它这句话说的太泛了,很容易误导人,虽然子 Shell 的确是当前 Shell 的子进程,但当前 Shell 的子进程不一定都是子 Shell(可能已经替换成了其他程序)。在 Bash 里面,只有特定的语法才会让代码进入子 Shell,比如管道两边的命令,比如用小括号括起来等等:

$ (echo $BASH_SUBSHELL)

1

$ ( ( ( ( (echo $BASH_SUBSHELL) ) ) ) )

5

真正的子 Shell 可以访问其父 Shell 的任何变量,而通过再执行一次 bash 命令所启动的 Shell 只能访问其父 Shell 传来的环境变量。 这篇教程 里面专门写了个例子:

For an example of the difference between a subshell and a child process that happens to be a shell:

unset a; a=1 (echo "a is $a in the subshell") sh -c 'echo "a is $a in the child shell"'

In the subshell, the regular shell variable a is visible; but because it is not exported, the full child process does not see it.

上面的例子中把当前 Shell 执行外部命令 sh 启动的 Shell 叫做 child shell,可惜在中文里还是得翻译成子 Shell。。。

从 c 语言层面讲,真正的子 Shell 是当前 Shell 进程调用了 fork() 函数,在内存中复制出一个几乎一模一样的子进程。而执行 bash 命令启动的所谓 child shell 是在执行 fork() 函数的基础上,又执行了一次 execve() 函数,execve() 函数会重新加载硬盘上的 bash 命令并执行,替换刚才 fork 出来的那个 shell 进程,除了传入的环境变量外,是个崭新的进程。

总结一下就是说,SHLVL 变量是记录了所谓的 child shell 的嵌套深度,而 BASH_SUBSHELL 是记录了 subshell 的嵌套深度。

把 child shell 叫成子 Shell,在口头上说说还可以,因为中文里没有其它什么好的叫法用来指代它,但你心里得明白,这不是术语子 Shell 真正的含义。

写到这里,我想这篇文章的标题应该改成“什么是子 Shell”了。

正文到此结束
Loading...