Op 18-10-16 om 00:15 schreef Martijn Dekker:
> Turns out you don't actually have to read ${.sh.subshell} twice: it is
> the output redirection within a command substitution that kills
> ${.sh.subshell}. This is finally starting to make some sense now.
> 
> $ echo $( (: 1>&1; echo ${.sh.subshell}) )
> 0
> 
> (expected output: 2)

Annnd it turns out that ${.sh.subshell} is not read-only. You can
actually assign a value to it! Very strange, but this makes the
workaround obvious: save the value before doing output redirection,
restore it afterwards.

$ echo $( (save=${.sh.subshell}; : 1>&1; .sh.subshell=$save; echo
${.sh.subshell}) )
2

In case anyone is interested, here's how I found the bug. First a little
simplified background information.

The cross-platform POSIX shell library I'm developing, "modernish"
<https://github.com/modernish/modernish>, includes a feature for robust
shell programming called "harden" that hardens a command against errors.
It does this by setting a shell function under the command's name that
first runs the real command, then automatically checks its exit status
against a user-specified value indicating a fatal error. This is my
attempt to provide something better than 'set -e' which is fundamentally
flawed.

If a fatal error is found, the function set by 'harden' calls another
modernish function, 'die'. Fatal errors should always kill the program,
so this function is designed to reliably halt program execution, even if
the error occurred within a subshell. To do this, "die" first checks if
we're currently in a subshell using a third function called
"insubshell". If we're not in a subshell, it simply exits. If we are, it
sends SIGTERM to the main shell ("$$") and then exits.

The aforementioned "insubshell" function has several platform-specific
versions; the correct one is automatically detected. For ksh93 I simply
have this:

   insubshell() {
        ((.sh.subshell))
   }

(returning false if ${.sh.subshell} is zero, true otherwise).

But this fails something in my test scripts: the hardened 'grep' (which,
remember, is a shell function calling the real grep) fails to kill the
program on error:
https://github.com/modernish/modernish/blob/master/share/doc/modernish/testsuite/harden-test

> harden -tp grep '> 1'   # harden and trace grep, whitelisting SIGPIPE
> # [...]
> print "this file has $(grep -c '.*'
/almost/certainly/a/non/existent/file) lines"
> print "we should never make it to here, BAD"

On ksh, 'die' fails to kill the main shell and the last line is printed,
because 'insubshell' fails to detect that 'die' is in a subshell.

And now I know why: since 'grep' was hardened with the -t option, the
'grep' shell function traces the command using output redirection before
doing its thing -- and this ksh bug is triggered by using output
redirection within a command substitution. Mystery solved.

Now on to implementing the necessary bug test (BUG_KSHSUBVAR) and
workarounds in modernish, so it once again becomes ksh compatible.

- M.

_______________________________________________
ast-developers mailing list
ast-developers@lists.research.att.com
http://lists.research.att.com/mailman/listinfo/ast-developers

Reply via email to