Сохранить коды выхода при захвате SIGINT и подобных?

14

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

Теперь это, вероятно, не будет иметь значения в реальном мире (например, потому что коды выхода не являются переносимыми и вдобавок к этому не всегда однозначными, как обсуждалось в Коде выхода по умолчанию, когда процесс завершается? ), Но все же мне интересно, есть ли там неужели нет способа предотвратить это и вместо этого вернуть код ошибки по умолчанию для прерванных сценариев?

Пример (в 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
PHK
источник
Ваш сценарий выглядит немного странно: вы говорите, readчтобы читать из текущего сопроцессора и trap cmd SIGINTне будет работать, поскольку стандарт говорит, что вы должны использовать trap cmd INT.
Щили
Ах да, под POSIX это, конечно, без префикса SIG.
phk
Упс, но тогда «read -p» также не будет поддерживаться, поэтому я собираюсь адаптировать его для bash.
phk
@schily: Я не знаю, что вы имеете в виду под «сопроцесс», хотя.
phk
Ну, на странице руководства Korn Shell написано, что она read -pчитает входные данные текущего процесса.
Щили

Ответы:

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. Я не верю, что обработчики сигналов являются рекурсивными.
скалистый
Хорошо, в вашем примере я вообще не перехватываю (SIG) INT, что на самом деле и нужно для очистки, даже если пользователь выходит из моего интерактивного скрипта через ctrl-c.
phk
@phk Хорошо. Хотя я думаю, что у вас есть представление о том, как заставить exit возвращать 0 или какое-то другое значение, с чем я столкнулся. Я предполагаю, что вы сможете изменить код так, чтобы параметр EXIT trap помещался в ваш настоящий обработчик очистки, который вызывается SIGINT.
скалистый
@rocky: Моя цель состояла в том, чтобы иметь обработчик ловушек, не изменяя код выхода, который я обычно имел бы без обработчика. Теперь я мог просто вернуть код выхода, который вы обычно получаете, но проблема заключалась в том, что я не знаю точного кода выхода в этих случаях (как видно из темы, на которую я ссылался, и сообщения от меня, это довольно сложно). Ваш пост все еще был полезен, я не думал о переопределении обработчика ловушек динамически.
phk
9

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

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

Если ваш сценарий прерывается при выполнении команды like 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

    р. В режиме Posix чтение может прерываться захваченным сигналом. После запуска обработчика прерываний read возвращает сигнал 128 + и выбрасывает любые частично прочитанные данные.

meuh
источник
ОК, это действительно странно. На интерактивной оболочке bash 4.3.42 (под cygwin) это 130, 0 в одной и той же оболочке в сценарии и в режиме POSIX или нет, без разницы. Но тогда под чертой и busybox это всегда 1.
phk
Я попробовал режим POSIX с ловушкой, которая не завершается, и он перезапускает read, как отмечено в файле CHANGES (добавлено в мой ответ). Таким образом, это может быть работа в процессе.
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, хотя
августа,
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/ ERR во внимание

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