作业控制真的应该在子 shell 和脚本中得到支持吗?

LL3*_*LL3 5 job-control shell-script subshell

场地小

\n\n

我经常使用子 shell 来执行涉及更改Shell 执行环境的操作,以免影响主 shell。我经常从交互式 shell 中执行此操作,有时也从脚本中执行此操作。

\n\n

启用或禁用作业控制当然是这些操作之一,无论出于何种原因,当需要精细控制进程分组时,我一直在随意使用此功能。

\n\n

然而,作为 Bash 用户,我注意到这种自由在最新版本中得到了加强:直到 v4.3,作业控制才被允许并在交互式子 shell 中完全工作,但自 v4.4 以来就不再这样了。在那里,它仍然可以在交互式子 shell 中使用,但不能完全工作(见下文)。它在脚本内仍然可以很好地工作,但是,自 v5 以来,至少一个作业控制的特定用例(即 的细粒度处理Ctrl+C)已经更加严格,使其只能从脚本内的子 shell 内进行管理..!

\n\n

因此,我开始产生怀疑,因此花了一些时间对一些常见的 shell 进行了示例综合测试,所有这些测试都是在 Ubuntu 19.04 上使用其测试 shell 的库存(和发行版更新)版本执行的。

\n\n

一些背景

\n\n

我注意到bash, yash, mksh, 和zshdo Honorset -m在子 shell 中,而dashkshdo 则没有。ksh即使在测试结束时被拦住也很奇怪。

\n\n

TL; DR:接下来是我刚才所说的漫长的演示会议:

\n\n
$ bash -c \'echo start; (set -bm; echo $-; sleep 3 & ps -s \'$$\' -o pid,ppid,pgid,tpgid,sid,s,cmd; wait); echo end\'\nstart\nbhmBc\n   PID   PPID   PGID  TPGID    SID S CMD\n 30147  30146  30147  31244  30147 S -bash\n 31241  30147  31241  31244  30147 S bash -c echo start; (set -bm; echo $-; sleep 3 & ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s\n 31242  31241  31241  31244  30147 S bash -c echo start; (set -bm; echo $-; sleep 3 & ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s\n 31243  31242  31243  31244  30147 S sleep 3\n 31244  31242  31244  31244  30147 R ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s,cmd\n[1]+  Done                    sleep 3\nend\n$\n$\n$ dash -c \'echo start; (set -bm; echo $-; sleep 3 & ps -s \'$$\' -o pid,ppid,pgid,tpgid,sid,s,cmd; wait); echo end\'\nstart\nbm\n   PID   PPID   PGID  TPGID    SID S CMD\n 30147  30146  30147  31245  30147 S -bash\n 31245  30147  31245  31245  30147 S dash -c echo start; (set -bm; echo $-; sleep 3 & ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s\n 31246  31245  31245  31245  30147 S dash -c echo start; (set -bm; echo $-; sleep 3 & ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s\n 31247  31246  31245  31245  30147 S sleep 3\n 31248  31246  31245  31245  30147 R ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s,cmd\nend     # \xc2\xab\xc2\xab 3 seconds correctly elapsed before getting here\n$\n$\n$ yash -c \'echo start; (set -bm; echo $-; sleep 3 & ps -s \'$$\' -o pid,ppid,pgid,tpgid,sid,s,cmd; wait); echo end\'\nstart\ncmb\n[1] + Running              sleep 3\n   PID   PPID   PGID  TPGID    SID S CMD\n 30147  30146  30147  31252  30147 S -bash\n 31249  30147  31249  31252  30147 S yash -c echo start; (set -bm; echo $-; sleep 3 & ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s\n 31250  31249  31249  31252  30147 S yash -c echo start; (set -bm; echo $-; sleep 3 & ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s\n 31251  31250  31251  31252  30147 S sleep 3\n 31252  31250  31252  31252  30147 R ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s,cmd\n[1] + Done                 sleep 3\nend\n$\n$\n$ mksh -c \'echo start; (set -bm; echo $-; sleep 3 & ps -s \'$$\' -o pid,ppid,pgid,tpgid,sid,s,cmd; wait); echo end\'\nstart\nmbhc\n   PID   PPID   PGID  TPGID    SID S CMD\n 30147  30146  30147  31253  30147 S -bash\n 31253  30147  31253  31253  30147 S mksh -c echo start; (set -bm; echo $-; sleep 3 & ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s\n 31254  31253  31253  31253  30147 S mksh -c echo start; (set -bm; echo $-; sleep 3 & ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s\n 31255  31254  31255  31253  30147 S mksh -c echo start; (set -bm; echo $-; sleep 3 & ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s\n 31256  31254  31256  31253  30147 R ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s,cmd\n[1] + Done                 \\sleep 3\nend\n$\n$\n$ ksh -c \'echo start; (set -bm; echo $-; sleep 3 & ps -s \'$$\' -o pid,ppid,pgid,tpgid,sid,s,cmd; wait); echo end\'\nstart\ncbhmsB\n   PID   PPID   PGID  TPGID    SID S CMD\n 30147  30146  30147  31258  30147 S -bash\n 31257  30147  31257  31258  30147 S ksh -c echo start; (set -bm; echo $-; sleep 3 & ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s,\n 31258  31257  31257  31258  30147 S ksh -c echo start; (set -bm; echo $-; sleep 3 & ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s,\n 31259  31258  31257  31258  30147 S ksh -c echo start; (set -bm; echo $-; sleep 3 & ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s,\n 31260  31258  31257  31258  30147 R ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s,cmd\n   # \xe2\x80\x94\xe2\x80\x94 3 seconds correctly elapsed on this empty line (which is *not* by me) \xe2\x80\x94\xe2\x80\x94\n[1]+  Stopped                 ksh -c \'echo start; (set -bm; echo $-; sleep 3 & ps -s \'$$\' -o pid,ppid,pgid,tpgid,sid,s,cmd; wait); echo end\'\n$\n$\n$ fg   # \xc2\xab\xc2\xab had to get rid of Stopped `ksh` from my login shell\nksh -c \'echo start; (set -bm; echo $-; sleep 3 & ps -s \'$$\' -o pid,ppid,pgid,tpgid,sid,s,cmd; wait); echo end\'\nend\n$\n$\n$ zsh -c \'echo start; (set -5m; echo $-; sleep 3 & ps -s \'$$\' -o pid,ppid,pgid,tpgid,sid,s,cmd; wait); echo end\'\nstart\n569Xm\n   PID   PPID   PGID  TPGID    SID S CMD\n 30147  30146  30147  31261  30147 S -bash\n 31261  30147  31261  31261  30147 S zsh -c echo start; (set -5m; echo $-; sleep 3 & ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s,\n 31262  31261  31261  31261  30147 S zsh -c echo start; (set -5m; echo $-; sleep 3 & ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s,\n 31263  31262  31263  31261  30147 S sleep 3\n 31264  31262  31264  31261  30147 R ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s,cmd\nend     # \xc2\xab\xc2\xab 3 seconds correctly elapsed before getting here\n$\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

根据上述各种方法的 TPGID 值ps,在 shell 中,set -m仅尊重bashyash在子 shell 内提供完全工作的作业控制环境,而mkshzsh则不这样做。

\n\n

事实上,在额外的测试中,mksh

\n\n
$ mksh -c \'echo start; (set -bm; echo $-; vim); echo end\'\nstart\nmbhc\nVim: Caught deadly signal HUP\nVim: Finished.\n   \xc2\xab\xc2\xab \xe2\x80\x94\xe2\x80\x94 cursor stopped here. Then I hit Return \xe2\x80\x94\xe2\x80\x94\n2R1: command not found\n95: command not found\n0c: command not found\n$\n
Run Code Online (Sandbox Code Playgroud)\n\n

尽管zsh

\n\n
$ zsh -c \'echo start; (set -5m; echo $-; vim); echo end\'\nstart\n569Xm      \xc2\xab\xc2\xab \xe2\x80\x94\xe2\x80\x94 cursor stopped here. Then I `killall zsh` from another terminal \xe2\x80\x94\xe2\x80\x94\nTerminated\n$ Vim: Caught deadly signal HUP\nVim: Finished.\n   \xc2\xab\xc2\xab \xe2\x80\x94\xe2\x80\x94 cursor stopped here. Then I hit Return \xe2\x80\x94\xe2\x80\x94\n2R1: command not found\n95: command not found\n0c: command not found\n$\n
Run Code Online (Sandbox Code Playgroud)\n\n

当然在我杀死它的父母vim之前处于停止状态:zsh

\n\n
$ mksh -c \'echo start; (set -bm; echo $-; vim); echo end\'\nstart\nmbhc\nVim: Caught deadly signal HUP\nVim: Finished.\n   \xc2\xab\xc2\xab \xe2\x80\x94\xe2\x80\x94 cursor stopped here. Then I hit Return \xe2\x80\x94\xe2\x80\x94\n2R1: command not found\n95: command not found\n0c: command not found\n$\n
Run Code Online (Sandbox Code Playgroud)\n\n

相反,bashyash都正确地完成了所有操作:echo start$-,启动完全可用的vim(包括光标键和Ctrl+C),并echo end在 Vim 退出后。

\n\n

然而,正如我在前提中所说的那样,当我直接从交互式 shell 中向bash它提供相同的示例子 shell 时,它崩溃了,显示了错误的 TPGID,就像和一样。在最后的测试中,只有\xc2\xa0 完全符合我的预期。mkshzshyash

\n\n

最重要的是,即使我将子外壳放在外部,仅保留内部,zsh -c并且mksh -c(即非交互式)效果也不佳。只有当我把它拿走时,它们才在子壳上工作得很好。这意味着即使在脚本中也不能与这些 shell 一起工作(我实际上测试了这些脚本,但它们失败了)。set -m vimvimset -mset -m

\n\n
\n\n

我必须承认,这些测试显示得如此混乱,以至于我也不确定上面使用的示例测试是否有一些错误的假设。因此我还尝试了一种更(据说)无害的:

\n\n
$ zsh -c \'echo start; (set -5m; echo $-; vim); echo end\'\nstart\n569Xm      \xc2\xab\xc2\xab \xe2\x80\x94\xe2\x80\x94 cursor stopped here. Then I `killall zsh` from another terminal \xe2\x80\x94\xe2\x80\x94\nTerminated\n$ Vim: Caught deadly signal HUP\nVim: Finished.\n   \xc2\xab\xc2\xab \xe2\x80\x94\xe2\x80\x94 cursor stopped here. Then I hit Return \xe2\x80\x94\xe2\x80\x94\n2R1: command not found\n95: command not found\n0c: command not found\n$\n
Run Code Online (Sandbox Code Playgroud)\n\n

它不起作用 \ trxc2\xa0got 立即停止,TPGID 坚持zsh进程。

\n\n

yash它与和 一起工作bash(当然,dash它根本不尊重set -m),而不是在mksh和 上工作ksh,尽管每个结果都与 不同zsh

\n\n
\n\n

最后回到我的问题

\n\n

除了固有的编程复杂性之外,作业控制真的意味着在子 shell 1中得到支持吗?也可能在脚本2内?(或者:我在这里没有看到什么?)

\n\n

1 POSIX 的Shell 执行环境似乎并没有禁止也没有强制执行它

\n\n

2摘自POSIX 描述set:“set -m选项 [...] 主要适用于交互式使用,而不是 shell 脚本应用程序。”\n

\n