Зберігати коди виходу при захопленні SIGINT та подібних?


14

Якщо я використовую, trapяк описано, наприклад, на http://linuxcommand.org/wss0160.php#trap, щоб увімкнути ctrl-c (або подібне) та очищення перед виходом, тоді я змінюю код повернення, який повертається.

Тепер це, мабуть, не матиме значення в реальному світі (наприклад, через те, що коди виходу не є портативними, і, крім того, не завжди однозначним, як обговорювалося в коді виходу за замовчуванням, коли процес закінчується? ), Але все одно мені цікаво, чи є насправді жодним чином не запобігти цьому і повернути код помилки за замовчуванням для перерваних сценаріїв замість цього?

Приклад (in bash, але моє питання не слід вважати специфічним для bash):

#!/bin/bash
trap 'echo EXIT;' EXIT
read -p 'If you ctrl-c me now my return code will be the default for SIGTERM. ' _
trap 'echo SIGINT; exit 1;' INT
read -p 'If you ctrl-c me now my return code will be 1. ' _

Вихід:

$ ./test.sh # doing ctrl-c for 1st read
If you ctrl-c me now my return code will be the default for SIGTERM.
$ echo $?
130
$ ./test.sh # doing ctrl-c for 2nd read
If you ctrl-c me now my return code will be the default for SIGTERM.
If you ctrl-c me now my return code will be 1. SIGINT 
EXIT
$ echo $?
1

(Відредаговано для видалення, щоб зробити його більш сумісним з POSIX.)

(Знову відредаговано, щоб зробити це сценарієм bash, моє запитання не стосується оболонки.)

Відредаговано для використання портативного "INT" для пастки на користь непортативного "SIGINT".

Відредаговано, щоб видалити непотрібні фігурні брекети та додати потенційне рішення.

Оновлення:

Я вирішив це зараз, просто вийшовши з деякими кодами помилок, жорстко закодованим і захопивши EXIT. Це може бути проблематично в певних системах, оскільки код помилки може відрізнятись або пастка EXIT неможлива, але в моєму випадку це все в порядку.

trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM

Ваш сценарій виглядає дещо дивно: ви скажете readчитати з поточного копроцесу і trap cmd SIGINTне буде працювати, як стандарт говорить, що ви повинні використовувати trap cmd INT.
schily

Ага так, під POSIX це звичайно без префікса SIG.
phk

На жаль, але "read -p" теж не підтримується, тому я збираюся адаптувати його до bash.
phk

@schily: Я не знаю, що ти маєш на увазі під "coprocess".
phk

Що ж, на сторінці користувача Korn Shell написано, що він read -pчитає дані з поточного копроцесу.
schily

Відповіді:


4

Все, що вам потрібно зробити - це змінити оброблювач EXIT всередині вашого очисника. Ось приклад:

#!/bin/bash
cleanup() {
    echo trapped exit
    trap 'exit 0' EXIT
}
trap cleanup EXIT
read -p 'If you ctrl-c me now my return code will be the default for SIGTERM. '

ти мав на увазі trap cleanup INTзамість trap cleanup EXIT?
Джефф Шаллер

Я думаю, я мав на увазі EXIT. Коли вихід нарешті викликається, однак це зроблено, ми змінюємо пастку на вихід із поверненням 0. Я не вірю, що обробники сигналів є рекурсивними.
rocky

Гаразд, у вашому прикладі я зовсім не затримую (SIG) INT, що насправді є тим, що я хочу, щоб зробити деяке очищення, навіть якщо користувач виходить з мого інтерактивного сценарію через ctrl-c.
phk

@phk Гаразд. Я думаю, хоч ви розумієте, як змусити вийти повернути 0 або якесь інше значення, яке те, що я зібрав, була проблемою. Я припускаю, що ви зможете скорегувати код, щоб помістити налаштування виходу в пастку всередині вашого справжнього оброблювача очищення, який викликається SIGINT.
скелястий

@rocky: Моя мета полягала в тому, щоб мати обробник пастки, не змінюючи код виходу, який, як правило, без обробника. Тепер я міг би просто повернути код виходу, який ви зазвичай отримаєте, але проблема полягала в тому, що я не знаю точного коду виходу в цих випадках (як видно з потоку, до якого я пов’язаний, і повідомлення від meuh це досить складно). Ваша публікація все ще була корисною, я не думав динамічно визначати обробник пасток.
phk

9

Насправді, переривання внутрішніх даних bash, readздається, трохи відрізняється від переривання команди, керованої bash. Зазвичай, коли ви вводите trap, $?встановлено, і ви можете зберегти його та вийти з тим же значенням:

trap 'rc=$?; echo $rc SIGINT; exit $rc' INT
trap 'rc=$?; echo $rc EXIT; exit $rc' EXIT

Якщо ваш скрипт буде перервано під час виконання команди на кшталт sleep або навіть вбудованого типу wait, ви побачите

130 SIGINT
130 EXIT

а вихідний код - 130. Однак, read -pздається, $?це 0 (у моїй версії bash 4.3.42 все одно).


Обробка сигналів під час роботи readможе тривати, відповідно до файлу змін у моєму випуску ... (/ usr / share / doc / bash / CHANGES)

зміни між цією версією, bash-4.3-alpha та попередньою версією, bash-4.2-release.

  1. Нові функції в Bash

    r. Перебуваючи в режимі Posix, режим "читання" переривається сигналом, що вловлюється. Після запуску обробника пастки зчитування повертає сигнал 128 + та викидає будь-який частково прочитаний вхід.


Гаразд, це дійсно дивно. Це 130 на інтерактивній оболонці на bash 4.3.42 (під cygwin), 0 під тією ж оболонкою, коли в режимі скрипту та POSIX або не має ніякої різниці. Але тоді під тире та
busbox

Я спробував режим POSIX з пасткою, яка не виходить, і він перезапускає read, як зазначено у файлі ЗМІНИ (доданий до моєї відповіді). Отже, це може бути незавершене виробництво.
meuh

Код виходу 130 - це 100% непортативний. У той час як Bourne Shell використовує 128 + signoяк вихідний код для сигналів, ksh93 використовує 256 + signo. POSIX говорить: щось вище за 128 ....
schily

@schily Правда, як зазначалося в темі, до якої я посилався в оригінальній публікації ( unix.stackexchange.com/questions/99112 ).
phk

6

Будь-який звичайний код виходу сигналу буде доступний $?після входу в обробник пасток:

sig_handler() {
    exit_status=$?  # Eg 130 for SIGINT, 128 + (2 == SIGINT)
    echo "Doing signal-specific up"
    exit "$exit_status"
}
trap sig_handler INT HUP TERM QUIT

Якщо є окрема пастка EXIT, ви можете використовувати ту саму методологію: негайно підтримуйте очищення статусу виходу, переданого з обробника сигналу (якщо такий є), а потім поверніть збережений статус виходу.


Це стосується більшості снарядів? Дуже хороший. localХоча це не POSIX.
phk

1
Відредаговано. Я не тестувався в інших {ba,z}sh. AFAIK, це найкраще, що можна зробити, незалежно.
Том Хейл

Це не працює в тирі ( $?це 1, який би не був отриманий сигнал).
MoonSweep

2

Просто повернути якийсь код помилки недостатньо, щоб імітувати вихід SIGINT. Я здивований, що поки ніхто про це не згадував. Подальше читання: https://www.cons.org/cracauer/sigint.html

Правильний спосіб:

for sig in EXIT ABRT HUP INT PIPE QUIT TERM; do
    trap "cleanup;
          [ $sig  = EXIT ] && normal_exit_only_cleanup;
          [ $sig != EXIT ] && trap - $sig EXIT && kill -s $sig $$
         " $sig
done

Це працює з Bash, Dash та zsh. Для подальшої переносимості вам потрібно використовувати числові специфікації сигналу (з іншого боку, zsh очікує параметр рядка для killкоманди ...)

Також зверніть увагу на особливу обробку EXITсигналу. Це пояснюється тим, що деякі оболонки (а саме Bash) також виконують пастки EXITдля будь-якого сигналу (після можливих визначених пасток цього сигналу). Скидання EXITпастки перешкоджає цьому.

Виконання коду лише при "нормальному" виході

Перевірка [ $sig = EXIT ]дозволяє виконувати код лише при нормальному (без сигналу) виході. Однак у всіх сигналах повинні бути пастки, які нарешті скидають пастку EXITпотім; normal_exit_only_cleanupбуде також викликано сигнали, які не роблять. Він також буде виконаний виходом наскрізь set -e. Це можна виправити, зачепившись ERR(що Dash не підтримується) та додавши чек [ $sig = ERR ]перед kill.

Спрощена версія Bash

З іншого боку, така поведінка означає, що в Bash можна просто зробити

trap cleanup EXIT

просто виконати якийсь код очищення та зберегти статус виходу.

відредаговано

  • Розробимо поведінку Баша щодо "EXIT захоплює все"

  • Видаліть сигнал KILL, який не може бути захоплений

  • Видаліть префікси SIG із назв сигналів

  • Не намагайтеся kill -s EXIT

  • Враховуйте set -e/ Помилка


Немає загальної вимоги, щоб програма, яка відловлює сигнали, закінчувалася таким чином, щоб зберегти той факт, що вона отримала сигнал. Наприклад, програма може оголосити, що відправка SIGINT- це спосіб її вимкнути, і вона може вирішити вийти з 0, якщо їй вдалося завершити без помилки або іншим кодом помилки, якщо вона не вимкнулася чисто. Справа в точці: top. Запустіть, top; echo $?а потім натисніть Ctrl-C. Статус, викинутий на екран, буде 0.
Луї

1
Дякую, що вказали на це. Однак плакат спеціально запитав про збереження коду виходу.
philipp2100
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.