«Ловушка ... INT TERM EXIT» действительно необходимо?

63

Много примеров для trapиспользования trap ... INT TERM EXITв задачах очистки. Но действительно ли необходимо перечислять все три сигсипа?

В руководстве сказано:

Если SIGNAL_SPEC - EXIT (0), ARG выполняется при выходе из оболочки.

что, я считаю, применимо независимо от того, закончился ли скрипт нормально или он закончился, потому что получил SIGINTили SIGTERM. Эксперимент также подтверждает мою веру:

$ cat ./trap-exit
#!/bin/bash
trap 'echo TRAP' EXIT
sleep 3
$ ./trap-exit & sleep 1; kill -INT %1
[1] 759
TRAP
[1]+  Interrupt               ./trap-exit
$ ./trap-exit & sleep 1; kill -TERM %1
[1] 773
TRAP
[1]+  Terminated              ./trap-exit

Тогда почему так много примеров перечисляют все INT TERM EXIT? Или я что-то пропустил и есть ли случай, когда подошва EXITпропустит?

musiphil
источник
3
Также имейте в виду, что с такой спецификацией, как INT TERM EXITкод очистки, выполняется дважды, когда SIGTERMили SIGINTполучен.
maxschlepzig

Ответы:

19

Спецификация POSIX не говорит много об условиях, приводящих к выполнению ловушки EXIT, а только о том, как должна выглядеть ее среда при выполнении.

В пепельном корпусе Busybox ваш тест выхода-ловушки не повторяет «TRAP» перед выходом из-за SIGINT или SIGTERM. Я подозреваю, что существуют другие оболочки, которые могут работать не так.

# /tmp/test.sh & sleep 1; kill -INT %1
# 
[1]+  Interrupt                  /tmp/test.sh
# 
# 
# /tmp/test.sh & sleep 1; kill -TERM %1
# 
[1]+  Terminated                 /tmp/test.sh
# 
Шон Дж. Гофф
источник
3
dashтакже не заманивает в ловушку только, EXITкогда это получает SIGINT/SIGTERM.
maxschlepzig
4
zshтакже - возможно, bashэто единственная оболочка, где EXITтакже совпадают сигналы.
maxschlepzig
@maxschlepzig zshне ловит , EXITкогда получает INT, но делает, когда получает TERM. РЕДАКТИРОВАТЬ: я только заметил, сколько лет это было ...
JoL
27

Да, есть разница.

Этот скрипт завершится, когда вы нажмете Enter, или отправите его SIGINTили SIGTERM:

trap '' EXIT
echo ' --- press ENTER to close --- '
read response

Этот скрипт завершится, когда вы нажмете Enter:

trap '' EXIT INT TERM
echo ' --- press ENTER to close --- '
read response

* Проверено в sh , Bash и Zsh . (больше не работает в sh при добавлении команды для запуска trap)


Есть также то, что сказал @Shawn: Эш и Дэш не улавливают сигналы EXIT.

Таким образом, для надежной обработки сигналов лучше всего избегать перехватов EXITи использовать что-то вроде этого:

cleanup() {
    echo "Cleaning stuff up..."
    exit
}

trap cleanup INT TERM
echo ' --- press ENTER to close --- '
read var
cleanup
Zaz
источник
1
Решение с очисткой делает правильную вещь - очень элегантно! Это стало идиомой для моих скриптов bash с mktempвызовами.
Бьерн Дальгрен
2
Это exitнеобходимо в cleanup?
Ярно
3
Это не сработает, если в вашем коде есть ошибки в сценарии, которые приводят к преждевременному завершению работы.
ijw
2
@ijw: в Bash и Ksh вы можете ловить, ERRчтобы справиться с этим, но он не переносим .
Заз
5
Это решение не является надежным, когда его вызывает другая оболочка. Это не обрабатывает ожидание при совместном выходе ; в trap - INT TERM; kill -2 $$качестве последней строки очистки вы захотите сообщить родительской оболочке о преждевременном выходе из нее. Если родительская оболочка foobar.sh вызывает ваш скрипт (foo.sh), а затем вызывает bar.sh, вы не хотите, чтобы bar.sh выполнялся, если INT / TERM отправляется на ваш foo.sh. trap cleanup EXITбудет обрабатывать это распространение автоматически, поэтому IMO является самым надежным. Это также означает, что вам не придется звонить cleanupв конце сценария.
Николас Пипитоне
12

Уточнение последнего ответа, потому что у него есть проблемы:

# Our general exit handler
cleanup() {
    err=$?
    echo "Cleaning stuff up..."
    trap '' EXIT INT TERM
    exit $err 
}
sig_cleanup() {
    trap '' EXIT # some shells will call EXIT after the INT handler
    false # sets $?
    cleanup
}
trap cleanup EXIT
trap sig_cleanup INT QUIT TERM

Очки выше:

Обработчики INT и TERM не закрываются для меня, когда я тестирую - они обрабатывают ошибку, затем оболочка возвращается к выходу (и это не так уж удивительно). Поэтому я гарантирую, что очистка завершается позже, и в случае сигналов всегда используется код ошибки (а в другом случае нормального выхода сохраняет код ошибки).

В bash кажется, что выход из обработчика INT также вызывает обработчик EXIT, поэтому я освобождаю обработчик выхода и вызываю его сам (который будет работать в любой оболочке независимо от поведения).

Я перехватываю выход, потому что сценарии оболочки могут выйти до того, как они достигнут дна - синтаксические ошибки, set -e и ненулевой возврат, просто вызывая выход. Вы не можете полагаться на шеллскрипт, добирающийся до основания.

SIGQUIT это Ctrl- \, если вы никогда не пробовали. Получает бонусный coredump. Поэтому я думаю, что это также стоит отловить, даже если это немного неясно.

Прошлый опыт говорит, что если вы (как и я) всегда нажимаете Ctrl-C несколько раз, вы иногда будете ловить его наполовину через часть очистки сценария оболочки, так что это работает, но не всегда так идеально, как вам бы хотелось.

IJW
источник
2
Вызывающий не просто получить 1 как код выхода, независимо от того , какой сигнал вызвал выход, в то время как Withour trapвызывающему получит 130 для SIGINT, 143 для SIGTERM и т.д. Так что я бы захватить и передать правильный код выхода , как: sig_cleanup() { err=$?; trap '' EXIT; (exit $err); cleanup; }.
Musiphil
2
Можете ли вы уточнить назначение trap '' EXIT INT TERMфункции очистки? Это чтобы предотвратить случайное прерывание пользователем очистки, о котором вы упоминали в предыдущем абзаце? Разве это не EXITизбыточно?
6