我很难很好地理解 Bash 管理子 shell 创建的方式以及相关的范围问题。希望有人能让我对这个问题的想法保持一致。
我不明白的第一件事是子 shell 中处理变量的方式。我认为变量不会被继承,除非它们打开了 eXport 标志。然而,对于通过分组创建的子 shell 来说,情况似乎并非如此:
$ n=3
$ ( pstree $$ ; echo $n )
bash---bash---pstree
3
相反,如果我手动创建子 shell,一切都会按预期进行:
$ export ppid=$$
$ bash
$ pstree $ppid
bash---bash---pstree
$ echo $n
另外,有些分组实际上并没有创建子 shell:
( pstree $$ )
bash---pstree
一些不应该的分组,实际上却是:
$ pstree $$ &
[1] 1685
$ bash---pstree
{ pstree $$; } &
[1] 1687
$ bash---bash---pstree
这对我来说似乎有点混乱,有很多特殊情况需要跟踪。而且它变得更加混乱。考虑流程替换:
$ cat <(pstree $$)
bash-+-bash---pstree
`-cat
在这里,根据我的理解,bash 在子进程中执行 cat,给它一个 FIFO 来读取。然后分叉一个子 shell,为其提供 FIFO 的另一端。子 shell 运行 pstree。
现在考虑:
$ pstree $$ > >(cat) # There is some non determinism involved, output may be different
bash---pstree---bash---cat
#or sometimes
bash---pstree---bash
这里 bash 似乎做了一些不同的事情。它fork了一个子shell,子shell又fork了另一个子shell,情况就变成:bash---bash(1)---bash(2)
bash(1)(获取 FIFO 的写入端)执行 pstree,bash(2)(获取 FIFO 的写入端)在子进程中运行 cat。
因此,在第一种情况下,子进程由主 shell 的子 shell 执行。第二个由主命令的 shell 子进程执行。
在我看来,第二种情况是因为 pstree 可能在创建 cat 之前运行。
答案1
我将尝试涵盖其中的大部分内容。
当您执行子 shell 时()
,$()
, <()
: bash 将调用fork()
.这将创建一个新的子进程,即一模一样作为父级,除了pid
、ppid
和 fork 的返回值(0 表示子级;正 pid,子级表示父级;负数表示错误 - 未创建子级)。因此,子 shell 将具有相同的状态/相同的变量。
当你调用 bash 时,shell 会调用fork()
then 它会调用exec("bash")
(实际上是变体之一)。这会将克隆的 bash 替换为新映像,并从头开始运行。因此变量被清除,配置文件被重新读取。
当你使用&
. Bash 调用fork()
的是后台进程。看起来它比需要的次数多了一次分叉,但可能正在使用分叉的 bash 来帮助管理工作。 (为什么不分叉是很便宜的)
对于最后一种情况pstree $$ > >(cat)
。 (这花了我更长的时间来解决):
Bash 分叉,一如既往,运行一个新进程。在调用 exec 之前,它需要重定向 stdin/out/err (在本例中只是 stdout)。为此,它必须cat
在子 shell 中运行,所以它确实如此。现在cat
是新狂欢的孩子。新的狂欢是旧的孩子。接下来 new-bash 调用exec
,并变为pstree
。