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

进程树是我所期望的:

但是,如果我的 bash -c 命令 部分是 单个 命令,例如:

$ bash -c 'sleep 10'

然后中间的sub-shell被吞掉了,我的交互式终端会话执行sleep "directly"作为children进程。 进程树如下所示:

\_ -bash \_ 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 在该字符串的最后一个命令处。
  • 命令完成后运行没有进一步的命令、陷阱、钩子等。
  • 退出状态不需要反转或以其他方式修改。
  • 无需取消重定向。

这可以节省内存,使进程的启动时间稍微快一些(因为它不需要 forked),并确保传递给 PID 的信号直接进入进程你是 运行ning,这使得 sh -c 'sleep 10' 的父级可以准确确定哪个信号被杀死 sleep,如果它实际上被信号杀死了。

然而,如果出于某种原因你想抑制它,你只需要设置一个陷阱——任何陷阱都可以:

# run the noop command (:) at exit
bash -c 'trap : EXIT; sleep 10'