Does trap work as expected while piping?

给你一囗甜甜゛ 提交于 2019-12-05 04:32:22

The only way to find out if this behavior is expected or not is to ask Chet Ramey (GNU bash maintainer). Please send an email with your report to bug-bash@gnu.org

You can see that the current behavior seems to be correct, given that it handles the subshell case explicitly in:http://git.savannah.gnu.org/cgit/bash.git/tree/execute_cmd.c#n621

/* We want to run the exit trap for forced {} subshells, and we
   want to note this before execute_in_subshell modifies the
   COMMAND struct.  Need to keep in mind that execute_in_subshell
   runs the exit trap for () subshells itself. */
/* This handles { command; } & */
s = user_subshell == 0 && command->type == cm_group && pipe_in == NO_PIPE && pipe_out == NO_PIPE && asynchronous;
/* run exit trap for : | { ...; } and { ...; } | : */
/* run exit trap for : | ( ...; ) and ( ...; ) | : */
s += user_subshell == 0 && command->type == cm_group && (pipe_in != NO_PIPE || pipe_out != NO_PIPE) && asynchronous == 0;

last_command_exit_value = execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close);
if (s)
    subshell_exit (last_command_exit_value);
else
    sh_exit (last_command_exit_value);

As you can see, the explicit subshell case is handled as a special case (and so is the case with the command grouping). This behavior has evolved historically, as Adrian found out, due to multiple bug reports.

This is the list of changes for this particular feature (triggering EXIT trap on subshells):


Commit: http://git.savannah.gnu.org/cgit/bash.git/commit/?id=a37d979e7b706ce9babf1306c6b370c327038eb9

+execute_cmd.c
+ - execute_command_internal: make sure to run the EXIT trap for group
+   commands anywhere in pipelines, not just at the end.  From a point
+   raised by Andreas Schwab <schwab@linux-m68k.org>

Report: https://lists.gnu.org/archive/html/bug-bash/2013-04/msg00126.html (Re: trap EXIT in piped subshell not triggered during wait)


Commit: http://git.savannah.gnu.org/cgit/bash.git/commit/?id=1a81420a36fafc5217e770e042fd39a1353a41f9

+execute_cmd.c
+ - execute_command_internal: make sure any subshell forked to run a
+   group command or user subshell at the end of a pipeline runs any
+   EXIT trap it sets.  Fixes debian bash bug 698411
+   http://bugs.debian.org/cgi-big/bugreport.cgi?bug=698411

Report: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=698411 (EXIT trap and pipeline and subshell)


Commit: http://git.savannah.gnu.org/cgit/bash.git/commit/?id=fd58d46e0d058aa983eea532bfd7d4c597adef54

+execute_cmd.c
+ - execute_command_internal: make sure to call subshell_exit for
+   {} group commands executed asynchronously (&).  Part of fix for
+   EXIT trap bug reported by Maarten Billemont <lhunath@lyndir.com>

Report: http://lists.gnu.org/archive/html/bug-bash/2012-07/msg00084.html (EXIT traps in interactive shells)


There is also a recent bug report in relation to the EXIT trap not executing in some expected contexts: http://lists.gnu.org/archive/html/bug-bash/2016-11/msg00054.html

Here are some more test cases for your amusement:

$ cat traps.sh
#!/bin/bash

echoTraps() {
        echo "entering echoTraps()"

        printf "  traps: %s\n" "$(trap -p)"

        echo "  setting trap"
        trap -- 'echo "func-exit()"' EXIT

        printf "  traps: %s\n" "$(trap -p)"

        echo "exiting echoTraps()"
}

trap -- 'echo "main-exit()"' EXIT

echo "===== calling '( echoTraps; )'"
( echoTraps; )
echo

echo "===== calling 'echoTraps | cat'"
echoTraps | cat
echo

echo "===== calling '( echoTraps; ) | cat'"
( echoTraps; ) | cat
echo

echo "===== calling '{ echoTraps; } | cat'"
{ echoTraps; } | cat
echo

bash-4.2.25(1)

$ ./traps.sh
===== calling '( echoTraps; )'
entering echoTraps()
  traps:
  setting trap
  traps: trap -- 'echo "func-exit()"' EXIT
exiting echoTraps()
func-exit()

===== calling 'echoTraps | cat'
entering echoTraps()
  traps: trap -- 'echo "main-exit()"' EXIT
  setting trap
  traps: trap -- 'echo "func-exit()"' EXIT
exiting echoTraps()

===== calling '( echoTraps; ) | cat'
entering echoTraps()
  traps:
  setting trap
  traps: trap -- 'echo "func-exit()"' EXIT
exiting echoTraps()
func-exit()

===== calling '{ echoTraps; } | cat'
entering echoTraps()
  traps: trap -- 'echo "main-exit()"' EXIT
  setting trap
  traps: trap -- 'echo "func-exit()"' EXIT
exiting echoTraps()

main-exit()

bash-4.3.0(1)

$ bash-static-4.3.2/bin/bash-static traps.sh
===== calling '( echoTraps; )'
entering echoTraps()
  traps:
  setting trap
  traps: trap -- 'echo "func-exit()"' EXIT
exiting echoTraps()
func-exit()

===== calling 'echoTraps | cat'
entering echoTraps()
  traps: trap -- 'echo "main-exit()"' EXIT
  setting trap
  traps: trap -- 'echo "func-exit()"' EXIT
exiting echoTraps()

===== calling '( echoTraps; ) | cat'
entering echoTraps()
  traps:
  setting trap
  traps: trap -- 'echo "func-exit()"' EXIT
exiting echoTraps()
func-exit()

===== calling '{ echoTraps; } | cat'
entering echoTraps()
  traps: trap -- 'echo "main-exit()"' EXIT
  setting trap
  traps: trap -- 'echo "func-exit()"' EXIT
exiting echoTraps()
func-exit()

main-exit()

Bottom line: Don't rely on edge-cases like this. I remember investigating other inconsistencies (not about traps) with regards to subshells and pipes and tried to wrap my head around the bash source code and you don't want to go down that route and try to understand why it behaves like it does in certain situations (the code is really horrible, btw). As you can see, some things seem to have been "fixed" and/but both my examples already behave differently than yours.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!