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

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

场地小

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

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

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

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

一些背景

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

TL;博士:接下来是我刚才所说的冗长的演示会议:

$ bash -c 'echo start; (set -bm; echo $-; sleep 3 & ps -s '$$' -o pid,ppid,pgid,tpgid,sid,s,cmd; wait); echo end'
start
bhmBc
   PID   PPID   PGID  TPGID    SID S CMD
 30147  30146  30147  31244  30147 S -bash
 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
 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
 31243  31242  31243  31244  30147 S sleep 3
 31244  31242  31244  31244  30147 R ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s,cmd
[1]+  Done                    sleep 3
end
$
$
$ dash -c 'echo start; (set -bm; echo $-; sleep 3 & ps -s '$$' -o pid,ppid,pgid,tpgid,sid,s,cmd; wait); echo end'
start
bm
   PID   PPID   PGID  TPGID    SID S CMD
 30147  30146  30147  31245  30147 S -bash
 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
 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
 31247  31246  31245  31245  30147 S sleep 3
 31248  31246  31245  31245  30147 R ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s,cmd
end     # «« 3 seconds correctly elapsed before getting here
$
$
$ yash -c 'echo start; (set -bm; echo $-; sleep 3 & ps -s '$$' -o pid,ppid,pgid,tpgid,sid,s,cmd; wait); echo end'
start
cmb
[1] + Running              sleep 3
   PID   PPID   PGID  TPGID    SID S CMD
 30147  30146  30147  31252  30147 S -bash
 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
 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
 31251  31250  31251  31252  30147 S sleep 3
 31252  31250  31252  31252  30147 R ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s,cmd
[1] + Done                 sleep 3
end
$
$
$ mksh -c 'echo start; (set -bm; echo $-; sleep 3 & ps -s '$$' -o pid,ppid,pgid,tpgid,sid,s,cmd; wait); echo end'
start
mbhc
   PID   PPID   PGID  TPGID    SID S CMD
 30147  30146  30147  31253  30147 S -bash
 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
 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
 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
 31256  31254  31256  31253  30147 R ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s,cmd
[1] + Done                 \sleep 3
end
$
$
$ ksh -c 'echo start; (set -bm; echo $-; sleep 3 & ps -s '$$' -o pid,ppid,pgid,tpgid,sid,s,cmd; wait); echo end'
start
cbhmsB
   PID   PPID   PGID  TPGID    SID S CMD
 30147  30146  30147  31258  30147 S -bash
 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,
 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,
 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,
 31260  31258  31257  31258  30147 R ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s,cmd
   # —— 3 seconds correctly elapsed on this empty line (which is *not* by me) ——
[1]+  Stopped                 ksh -c 'echo start; (set -bm; echo $-; sleep 3 & ps -s '$$' -o pid,ppid,pgid,tpgid,sid,s,cmd; wait); echo end'
$
$
$ fg   # «« had to get rid of Stopped `ksh` from my login shell
ksh -c 'echo start; (set -bm; echo $-; sleep 3 & ps -s '$$' -o pid,ppid,pgid,tpgid,sid,s,cmd; wait); echo end'
end
$
$
$ zsh -c 'echo start; (set -5m; echo $-; sleep 3 & ps -s '$$' -o pid,ppid,pgid,tpgid,sid,s,cmd; wait); echo end'
start
569Xm
   PID   PPID   PGID  TPGID    SID S CMD
 30147  30146  30147  31261  30147 S -bash
 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,
 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,
 31263  31262  31263  31261  30147 S sleep 3
 31264  31262  31264  31261  30147 R ps -s 30147 -o pid,ppid,pgid,tpgid,sid,s,cmd
end     # «« 3 seconds correctly elapsed before getting here
$

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

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

$ mksh -c 'echo start; (set -bm; echo $-; vim); echo end'
start
mbhc
Vim: Caught deadly signal HUP
Vim: Finished.
   «« —— cursor stopped here. Then I hit Return ——
2R1: command not found
95: command not found
0c: command not found
$

尽管zsh

$ zsh -c 'echo start; (set -5m; echo $-; vim); echo end'
start
569Xm      «« —— cursor stopped here. Then I `killall zsh` from another terminal ——
Terminated
$ Vim: Caught deadly signal HUP
Vim: Finished.
   «« —— cursor stopped here. Then I hit Return ——
2R1: command not found
95: command not found
0c: command not found
$

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

$ ps -t 0 -o pid,ppid,pgid,tpgid,sid,s,cmd
   PID   PPID   PGID  TPGID    SID S CMD
 30147  30146  30147  31582  30147 S -bash
 31582  30147  31582  31582  30147 S zsh -c echo start; (set -5m; echo $-; vim); echo end
 31583  31582  31583  31582  30147 T vim
$
$ killall zsh
$

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

然而,正如我在前提中所说的,bash当我给它提供相同的示例子外壳时,它崩溃了直接地从交互式 shell 中,显示错误的 TPGID,就像 和mksh一样zsh。在这次最后的测试中,yash 一切都按照我的预期进行。

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


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

#!/usr/bin/zsh

set -m
echo start
echo $-
tr '[a-z]' '[A-Z]'
echo end

确实如此不是工作:tr 立即停止,TPGID 继续zsh进行。

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


最后回到我的问题

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

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

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

相关内容