Работает ли ловушка должным образом при прокладке трубопровода?

Вот минимальный код для демонстрации проблемы: http://pastebin.com/5TXDpSh5

#!/bin/bash
set -e
set -o pipefail

function echoTraps() {
    echo "= on start:"
    trap -p
    trap -- 'echo func-EXIT' EXIT
    echo "= after set new:"
    trap -p
    # we can ensure after script done - file '/tmp/tmp.txt' was not created
    trap -- 'echo SIG 1>/tmp/tmp.txt' SIGPIPE SIGHUP SIGINT SIGQUIT SIGTERM
}

trap -- 'echo main-EXIT1' EXIT

echo "===== subshell trap"
( echoTraps; )

echo "===== pipe trap"
echoTraps | cat

echo "===== done everything"

выход

===== subshell trap
= on start:
= after set new:
trap -- 'echo func-EXIT' EXIT
func-EXIT
===== pipe trap
= on start:
= after set new:
trap -- 'echo func-EXIT' EXIT
===== done everything
main-EXIT1

ожидаемый результат

===== subshell trap
= on start:
= after set new:
trap -- 'echo func-EXIT' EXIT
func-EXIT
===== pipe trap
= on start:
= after set new:
trap -- 'echo func-EXIT' EXIT
func-EXIT                 <---- here is the expected difference
===== done everything
main-EXIT1

NB: я тестировал для OSX 10.9.2 bash (3.2.51) - другие версии bash имеют такую ​​​​же разницу между фактическим и ожидаемым результатом и описанным ниже


person Maxim Kholyavkin    schedule 06.03.2014    source источник
comment
Хороший вопрос. Я подозреваю состояние гонки между выходом двух сторон конвейера, но я не могу точно указать на это.   -  person chepner    schedule 06.03.2014
comment
Похоже на ошибку bash для меня. Вы можете сообщить об этом вверх по течению.   -  person user2719058    schedule 06.03.2014
comment
Кроме того, function foo() { сочетает синтаксис POSIX sh и ksh88 таким образом, что ни один из них не совместим. Лучше использовать либо foo() { (маршрут POSIX), либо function foo { (маршрут ksh, хотя обратите внимание, что bash не реализует специальную семантику для функций, объявленных таким образом, которую имеет ksh, что делает этот синтаксис несколько вводит в заблуждение читателей, которые могут ожидать локальные переменные по умолчанию и т. д.).   -  person Charles Duffy    schedule 27.01.2018


Ответы (2)


Единственный способ узнать, ожидается ли такое поведение, — это спросить Чета Рэми (сопровождающего GNU bash). Отправьте электронное письмо с отчетом на адрес [email protected].

Вы можете видеть, что текущее поведение кажется правильным, учитывая, что он явно обрабатывает случай подоболочки в: 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);

Как видите, явный случай подоболочки обрабатывается как особый случай (как и случай с группировкой команд). Как выяснил Адриан, такое поведение исторически сложилось из-за многочисленных сообщений об ошибках.

Это список изменений для этой конкретной функции (срабатывание ловушки EXIT на подоболочках):


Коммит: 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 <[email protected]>

Отчет: https://lists.gnu.org/archive/html/bug-bash/2013-04/msg00126.html (Re: ловушка EXIT в конвейерной подоболочке не срабатывает во время ожидания)


Коммит: 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

Отчет: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=698411 (ловушка EXIT, конвейер и подоболочка)


Коммит: 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 <[email protected]>

Отчет: http://lists.gnu.org/archive/html/bug-bash/2012-07/msg00084.html (ловушки EXIT в интерактивных оболочках)


Существует также недавний отчет об ошибке, связанный с тем, что ловушка EXIT не выполняется в некоторых ожидаемых контекстах: http://lists.gnu.org/archive/html/bug-bash/2016-11/msg00054.html.

person dualbus    schedule 13.01.2017

Вот еще несколько тестов для вашего развлечения:

$ 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

Баш-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()

Баш-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()

Итог: не полагайтесь на такие крайние случаи. Я помню, как исследовал другие несоответствия (не о ловушках) в отношении подоболочек и каналов и пытался разобраться в исходном коде bash, и вы не хотите идти по этому пути и пытаться понять, почему он ведет себя так, как в некоторых случаях. ситуации (код действительно ужасен, кстати). Как видите, некоторые вещи, кажется, были «исправлены» и/или оба моих примера уже ведут себя не так, как ваш.

person Adrian Frühwirth    schedule 06.03.2014
comment
Спасибо за расследование. Я использовал другую версию bash, поэтому я обновил свой вопрос. В любом случае ваш пример имеет такую ​​же разницу между фактическим и ожидаемым результатом для случая echoTraps | cat. - person Maxim Kholyavkin; 06.03.2014