Bash 'swallowing' sub-shell children 执行单个命令时的进程
Bash 'swallowing' sub-shell children process when executing a single command
遇到了意想不到的 bash
/sh
行为,我想知道有人可以解释其背后的基本原理,并提供以下问题的解决方案。
在交互式 bash
shell 会话中,我执行:
$ bash -c 'sleep 10 && echo'
ps
在 Linux 上看起来像这样:
\_ -bash
\_ bash -c sleep 10 && echo
\_ sleep 10
进程树是我所期望的:
- 我的互动bashshell过程(
$
)
- 一个childrenshell进程(
bash -c ...
)
- 一个休眠children进程
但是,如果我的 bash -c
的 命令 部分是 单个 命令,例如:
$ bash -c 'sleep 10'
然后中间的sub-shell被吞掉了,我的交互式终端会话执行sleep "directly"作为children进程。
进程树如下所示:
\_ -bash
\_ sleep 10
所以从进程树的角度来看,这两个产生相同的结果:
$ bash -c 'sleep 10'
$ sleep 10
这是怎么回事?
现在回答我的问题:有没有办法强制中间 shell,而不管传递给 bash -c ...
的表达式的复杂性?
(我可以将 ; echo;
之类的内容附加到我的实际命令和 "works",但我宁愿不这样做。是否有更合适的方法来强制中间过程存在?
(编辑:ps
输出中的拼写错误;按照评论中的建议删除了 sh
标签;又一个拼写错误)
实际上有一条评论 in the bash source 描述了此功能的大部分原理:
/* If this is a simple command, tell execute_disk_command that it
might be able to get away without forking and simply exec.
This means things like ( sleep 10 ) will only cause one fork.
If we're timing the command or inverting its return value, however,
we cannot do this optimization. */
if ((user_subshell || user_coproc) && (tcom->type == cm_simple || tcom->type == cm_subshell) &&
((tcom->flags & CMD_TIME_PIPELINE) == 0) &&
((tcom->flags & CMD_INVERT_RETURN) == 0))
{
tcom->flags |= CMD_NO_FORK;
if (tcom->type == cm_simple)
tcom->value.Simple->flags |= CMD_NO_FORK;
}
在bash -c '...'
情况下,CMD_NO_FORK
标志在builtins/evalstring.c
中的should_suppress_fork
function确定时设置。
让shell这样做总是对您有利。仅在以下情况下发生:
- 输入来自硬编码字符串,shell 在该字符串的最后一个命令处。
- 命令完成后运行没有进一步的命令、陷阱、钩子等。
- 退出状态不需要反转或以其他方式修改。
- 无需取消重定向。
这可以节省内存,使进程的启动时间稍微快一些(因为它不需要 fork
ed),并确保传递给 PID 的信号直接进入进程你是 运行ning,这使得 sh -c 'sleep 10'
的父级可以准确确定哪个信号被杀死 sleep
,如果它实际上被信号杀死了。
然而,如果出于某种原因你想抑制它,你只需要设置一个陷阱——任何陷阱都可以:
# run the noop command (:) at exit
bash -c 'trap : EXIT; sleep 10'
遇到了意想不到的 bash
/sh
行为,我想知道有人可以解释其背后的基本原理,并提供以下问题的解决方案。
在交互式 bash
shell 会话中,我执行:
$ bash -c 'sleep 10 && echo'
ps
在 Linux 上看起来像这样:
\_ -bash
\_ bash -c sleep 10 && echo
\_ sleep 10
进程树是我所期望的:
- 我的互动bashshell过程(
$
) - 一个childrenshell进程(
bash -c ...
) - 一个休眠children进程
但是,如果我的 bash -c
的 命令 部分是 单个 命令,例如:
$ bash -c 'sleep 10'
然后中间的sub-shell被吞掉了,我的交互式终端会话执行sleep "directly"作为children进程。 进程树如下所示:
\_ -bash
\_ sleep 10
所以从进程树的角度来看,这两个产生相同的结果:
$ bash -c 'sleep 10'
$ sleep 10
这是怎么回事?
现在回答我的问题:有没有办法强制中间 shell,而不管传递给 bash -c ...
的表达式的复杂性?
(我可以将 ; echo;
之类的内容附加到我的实际命令和 "works",但我宁愿不这样做。是否有更合适的方法来强制中间过程存在?
(编辑:ps
输出中的拼写错误;按照评论中的建议删除了 sh
标签;又一个拼写错误)
实际上有一条评论 in the bash source 描述了此功能的大部分原理:
/* If this is a simple command, tell execute_disk_command that it
might be able to get away without forking and simply exec.
This means things like ( sleep 10 ) will only cause one fork.
If we're timing the command or inverting its return value, however,
we cannot do this optimization. */
if ((user_subshell || user_coproc) && (tcom->type == cm_simple || tcom->type == cm_subshell) &&
((tcom->flags & CMD_TIME_PIPELINE) == 0) &&
((tcom->flags & CMD_INVERT_RETURN) == 0))
{
tcom->flags |= CMD_NO_FORK;
if (tcom->type == cm_simple)
tcom->value.Simple->flags |= CMD_NO_FORK;
}
在bash -c '...'
情况下,CMD_NO_FORK
标志在builtins/evalstring.c
中的should_suppress_fork
function确定时设置。
让shell这样做总是对您有利。仅在以下情况下发生:
- 输入来自硬编码字符串,shell 在该字符串的最后一个命令处。
- 命令完成后运行没有进一步的命令、陷阱、钩子等。
- 退出状态不需要反转或以其他方式修改。
- 无需取消重定向。
这可以节省内存,使进程的启动时间稍微快一些(因为它不需要 fork
ed),并确保传递给 PID 的信号直接进入进程你是 运行ning,这使得 sh -c 'sleep 10'
的父级可以准确确定哪个信号被杀死 sleep
,如果它实际上被信号杀死了。
然而,如果出于某种原因你想抑制它,你只需要设置一个陷阱——任何陷阱都可以:
# run the noop command (:) at exit
bash -c 'trap : EXIT; sleep 10'