“Пастка… ІНТЕРМІН-ВИХІД” дійсно потрібна?


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. EDIT: Я щойно помітив, скільки років це було ...
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, коли ви додаєте команду для запуску пастки)


Є також те, що сказав @Shawn: Ash та Dash не вловлюють сигнали EXIT.

Тож, щоб сильно обробляти сигнали, краще EXITвзагалі уникати пасток і використовувати щось подібне:

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

trap cleanup INT TERM
echo ' --- press ENTER to close --- '
read var
cleanup

1
Рішення з очищенням робить правильно - дуже елегантно! Це стало ідіомою для моїх баш сценаріїв з mktempдзвінками.
Бьорн Далгрен

2
Це exitпотрібно в cleanup?
jarno

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зайве?
Шість
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.