«Ловушка ... 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пропустит?


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

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 несколько раз, вы иногда будете ловить его наполовину через часть очистки сценария оболочки, так что это работает, но не всегда так идеально, как вам бы хотелось.


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

2
Можете ли вы уточнить назначение trap '' EXIT INT TERMфункции очистки? Это чтобы предотвратить случайное прерывание пользователем очистки, о котором вы упоминали в предыдущем абзаце? Разве это не EXITизбыточно?
— 6
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.