我正在阅读 bash 手册,并看到一个部分指出:
“命令替换、用括号分组的命令以及异步命令在与 shell 环境重复的子 shell 环境中调用”
我的问题是,有没有办法可以真正看到子 shell 中执行的内容。例如,假设我写了下面的行。我需要做什么才能实际查看子 shell 中执行的内容?
$ printf "first\nsecond\nthird" | grep -E "(first|second)"
我如何检查子 shell 中实际执行的内容(我怀疑它对“第一个”执行 grep,然后对第二个 shell 对“第二个”执行 grep,但我只想看看它在做什么)。
答案1
我认为你不能直接看到它。
xtrace
( set -x
) 确实显示eval
s 和命令替换的嵌套级别,但不显示括号中的组。
Bash 在子进程中运行子 shell,所以原则上你可以尝试运行strace
或类似地查看fork()
(clone()
在Linux上)和execve()
系统调用shell及其子进程,但输出有点难以解释。此外,像这样的内置函数printf
不会出现在execve()
调用中,并且当 Bash 进程只有一个要运行的命令时,它会跳过一级分叉。例如,bash -c '/bin/false'
不会对外部命令进行分叉,同样,(/bin/false)
只有一个对子 shell 的分叉,而不是对子 shell 和外部命令的两个分叉。
您还可以$BASHPID
在不同的地方打印它,并将其与$$
.$BASHPID
包含当时实际运行的 Bash 进程的 PID,同时$$
包含主 Bash 进程的 PID,并且不会更新子 shell。虽然它没有显示运行的命令,但散布这些打印输出有点混乱:
$ echo $$ >&2; { echo "lhs: $BASHPID" >&2; printf foobar; } | { echo "rhs: $BASHPID" >&2; grep oo; }
30163
rhs: 30166
lhs: 30165
foobar
是的,我们可以从输出中看出管道的两个部分都在子外壳中运行,即使您引用的部分中没有提到这一点。
不管怎样,在子shell中运行的主要是那些,你引用提到的三个,加上管道。
异步/后台进程很明显:它们与 shell 同时运行,因此它们不能位于同一进程中。管道的各个部分也必须同时运行,因此最多可以在主 shell 进程中运行。 (在 Bash 中,如果选项已设置且有效,最右边的部分将执行此操作lastpipe
,否则它们都会获得自己的子 shell/进程。)
带括号的组明确要求使用子 shell,而且,我想不出命令替换的借口,所以我想我们只能接受它们也这样做。
子 shell 中的更改不会传播到主 shell,因此测试它也相对简单。例如,如果你运行:
bash -c 's=
s+=direct.
(s+=parens.)
$(s+=comsub.)
s+=pipeleft. | s+=piperight.
s+=async. &
eval s+=eval.
echo $s'
你得到了输出direct.eval.
,所以这些是没有在子 shell 中运行。其余的都做了。如果您-O lastpipe
在 之前添加-c
,或shopt -s lastpipe;
在脚本内添加,您还会看到piperight
已添加到列表中。
答案2
首先,我不确定grep
exec 是否在两个子 shell 中。
但要检查执行命令时发生的情况,可以使用 command truss
。就像是:
printf "first\nsecond\nthird" | truss -o out.log grep -E "(first|second)"
将存储在out.log
系统调用、信号等命令中。
这里是手册页的truss
以供将来参考。
关于strace
我认为你可以在命令中替换它:
printf "first\nsecond\nthird" | strace -o out.log grep -E "(first|second)"
但请注意,与truss
如果您想跟踪整个 shell、子 shell 创建、系统调用等,您应该使用以下格式的命令:
strace -o out.log 'printf "first\nsecond\nthird" | grep -E "(first|second)"'