5

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."

LL3
  • 5,418

0 Answers0