Small premise
I use subshells quite often to perform operations that involve changing the Shell Execution Environment, so as not to affect the main shell. I do it often from an interactive shell, and sometimes also from scripts.
Enabling or disabling job-control is of course one of these operations, and I've been using this feature at will when needed to fine control the grouping of processes for whatever reason.
However, as a Bash user, I noticed that this liberty has been tightened over the latest releases: until v4.3, job-control was allowed and fully working from within interactive subshells, but no more since v4.4. There it is still allowed in an interactive subshell, but will not work fully (see below). It does still work well from within scripts but, since v5, at least one particular use case for job-control (namely the fine-grained handling of Ctrl+C) has been tightened even more, rendering it manageable only from within subshells within scripts..!
I therefore started being doubtful, and thus spent some time over a sample synthesized test on some common shells, all performed on Ubuntu 19.04 using its stock (and distribution updated) versions of the shells tested.
A bit of context
I noticed that bash
, yash
, mksh
, and zsh
do honor set -m
within subshells, while dash
and ksh
do not. ksh
even with the added weirdness of getting stopped at the end of its test.
TL; DR: Follows a looong demonstrative session of what I just said:
$ 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
$
According to the TPGID value by the various ps
above, among the shells that did honor set -m
only bash
and yash
provided a fully working job-control environment within a subshell, while mksh
and zsh
did not.
In fact, on an additional test, 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
$
while 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
$
and of course vim
was in stopped state before I killed its zsh
parent:
$ 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
$
Both bash
and yash
instead, did everything correctly: echo start
and $-
, start a fully usable vim
(including cursor keys and Ctrl+C), and echo end
after Vim exited.
However, as I said in premise, bash
fell apart when I fed it the same sample subshell directly from the interactive shell, displaying an incorrect TPGID just like mksh
and zsh
did. At this final test, only yash
did all as I expected.
On top of everything, zsh -c
and mksh -c
(i.e. non-interactive) did not well even when I placed the set -m
outside the subshell leaving only vim
inside. They did work well on a subshelled vim
only when I took the set -m
away. Meaning that set -m
does not work with these shells even in scripts (which I then in fact tested, and they failed).
I must confess that these tests display such a mess, that I've also become uncertain as to whether the sample test I used above has some wrong assumption. Therefore I also tried a more (supposedly) innocuous:
#!/usr/bin/zsh
set -m
echo start
echo $-
tr '[a-z]' '[A-Z]'
echo end
and it did not work: tr
got stopped immediately, TPGID stick on zsh
process.
It worked with yash
and bash
, (and of course dash
which doesn't honor set -m
at all), not on mksh
and ksh
though each with yet different outcomes than zsh
.
Finally back to my question
Aside the programming complications that are inherently involved, is job-control really meant to be supported within subshells1 ? and possibly also within scripts2 ? (or else: what am I not seeing here ?)
1POSIX's Shell Execution Environment seems not to forbid nor mandate it
2Excerpt from POSIX description of set
: "The set -m
option [...] applies primarily to interactive use, not shell script applications."