【问题标题】:Bash 'swallowing' sub-shell children process when executing a single command执行单个命令时 Bash '吞咽' 子 shell 子进程
【发布时间】:2017-11-18 11:52:30
【问题描述】:

遇到了意想不到的bash/sh 行为,我想知道有人可以解释其背后的原因,并为以下问题提供解决方案。

在交互式bash shell 会话中,我执行:

$ bash -c 'sleep 10 && echo'

在 Linux 上使用ps 看起来像这样:

\_ -bash \_ bash -c sleep 10 && echo \_ sleep 10

进程树是我所期望的:

  • 我的交互式 bash shell 进程 ($)
  • 一个子 shell 进程 (bash -c ...)
  • 睡眠子进程

但是,如果我的bash -ccommand 部分是 single 命令,例如:

$ bash -c 'sleep 10'

然后中间的子外壳被吞下,我的交互式终端会话作为子进程“直接”执行睡眠。 进程树如下所示:

\_ -bash \_ sleep 10

所以从进程树的角度来看,这两个产生相同的结果:

  • $ bash -c 'sleep 10'
  • $ sleep 10

这是怎么回事?

现在我的问题是:有没有办法强制中间 shell,不管传递给 bash -c ... 的表达式的复杂性如何?

(我可以将; echo; 之类的内容附加到我的实际命令中并且“有效”,但我不想这样做。有没有更合适的方法来强制中间过程存在?)

(编辑:ps 输出中的错字;删除了 cmets 中建议的 sh 标签;又一个错字)

【问题讨论】:

  • 如果可能,您为什么想要这种优化?
  • 在用户可以传入任意命令的环境中处理子进程时,主要确保行为一致。我不确定绕过这个优化是我的解决方案(我遇到的实际问题必须处理 sudo 的更改:stackoverflow.com/a/34376188)。但这种行为很有趣,我想了解更多。
  • 好问题。你的意思是在你的第一个 ps 树的第 2 行说 \_ bash -c sleep 10 && echo 而不是 \_ bash -c sleep 10 && sleep 10
  • @codeforester 确实,谢谢

标签: linux bash shell subshell


【解决方案1】:

实际上有一条评论 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 的信号直接进入您正在运行的进程,使sh -c 'sleep 10' 的父级可以准确地确定哪个信号杀死了sleep,如果它实际上被信号杀死了。

但是,如果出于某种原因你想禁止它,你需要设置一个陷阱——任何陷阱都可以:

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

【讨论】:

  • 我无法从源头上分辨出来,但在我看来,只有在整个 -c 命令行上只有一个命令时,Bash 才会这样做。所以像bash -c ":; sleep 10" 这样的东西即使在它开始睡眠后也会让shell继续运行。
  • @ilkkachu,我的直觉预期是,这可能会因版本而异——3.2 将在 should_suppress_fork 的 2014 年末实施之前,f/e。即使今天是这种情况,我也不希望它在未来的版本中仍然如此(这有望解决优化的缺失机会)——而陷阱将总是需要保留外壳正在运行。
  • 似乎在 3.2 和 4.4 中的行为相似。
  • 点头。了解当前行为很有用,但我仍然怀疑它是否值得信赖。如果一个可以通过在最后一个命令中使用隐式exec 使脚本运行得更快(当然,在典型情况下只有几毫秒,但仍然如此),为什么切特会拒绝那个如果有人要写补丁?
  • 谢谢。如果您想知道为什么我要强制该中间外壳在所有情况下都存在,那么答案与此处提到的sudo 的更改有关:stackoverflow.com/a/34376188。对 sudo 的杀戮曾经被转发给它的子进程,但现在不再如此,所以我试图在不同的进程组中生成我的进程。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-11-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-18
相关资源
最近更新 更多