att / ast

AST - AT&T Software Technology
Eclipse Public License 1.0
559 stars 152 forks source link

The output of `trap` cannot be piped or redirected #469

Open krader1961 opened 6 years ago

krader1961 commented 6 years ago

While working on a fix for #429 I noticed that the builtins unit test failed in a surprising manner. Both the current ksh version and ksh93u+ does not allow you to pipe the output of trap (to list existing traps) to another command:

$ trap 'print TERM' TERM
$ trap
trap -- 'print TERM' TERM
$ trap | cat
$

That deserves a WTF. :smile:

krader1961 commented 6 years ago

Note that you can capture the output in a var:

$ trap 'print TERM' TERM
$ x=$(trap)
$ typeset -p x
x=$'trap -- \'print TERM\' TERM'
$

But not if you pipe trap's output through a pipe:

$ trap 'print TERM' TERM
$ x=$(trap | cat)
$ typeset -p x
x=''
$

This is true for ksh93u+ as well as the current, beta, version. So this bug has been present for at least 25 years.

kernigh commented 6 years ago

dash and pdksh/mksh also can't pipe the trap output. mksh(1) says,

     trap [handler signal ...]
     ...
            With no arguments, the current state of the traps that have been
            set since the shell started is shown as a series of trap commands.
            Note that the output of trap cannot be usefully piped to another
            process (an artifact of the fact that traps are cleared when sub-
            processes are created).

dash(1) says,

     trap [action signal ...]
            ...  When the shell forks off a subshell,
            it resets trapped (but not ignored) signals to the default
            action....

POSIX trap says,

When a subshell is entered, traps that are not being ignored shall be set to the default actions, except in the case of a command substitution containing only a single trap command, when the traps need not be altered. Implementations may check for this case using only lexical analysis; for example, if `trap` and $( trap -- ) do not alter the traps in the subshell, cases such as assigning var=trap and then using $($var) may still alter them. This does not imply that the trap command cannot be used within the subshell to set new traps.

The trap command with no operands shall write to standard output a list of commands associated with each condition. If the command is executed in a subshell, the implementation does not perform the optional check described above for a command substitution containing only a single trap command, and no trap commands with operands have been executed since entry to the subshell, the list shall contain the commands that were associated with each condition immediately before the subshell environment was entered.

bash and zsh can pipe the trap output:

$ bash -c 'trap "print TERM" TERM; trap | cat'
trap -- 'print TERM' SIGTERM
$ zsh -c 'trap "print TERM" TERM; trap | cat'  
trap -- 'print TERM' TERM

zsh stops piping the trap output if I wrap trap in another subshell, put bash continues to pipe the output even if I do something else in that subshell:

$ zsh -c 'trap "print TERM" TERM; (trap) | cat'
$ bash -c 'trap "print TERM" TERM; (trap) | cat'
trap -- 'print TERM' SIGTERM
$ bash -c 'trap "print TERM" TERM; (awk -f /dev/null; trap) | cat'
trap -- 'print TERM' SIGTERM
krader1961 commented 6 years ago

That makes some sense, @kernigh. Thanks for looking up that info. So it may be that the current behavior isn't really a bug but an expected artifact of how the various features are implemented.

I'd be willing to live with the current behavior if it were better documented. But I'd be happier if it were possible to pipe the output of trap -p to another command.