Ловушка bash и замена процесса
ОБНОВИТЬ
Я использовал гораздо лучший тестовый пример для ответа, который я написал. Я добавляю сюда обновленный тестовый случай, если кто-то захочет поэкспериментировать:
#!/bin/bash
mypts="$( tty )"
# main traps
trap "echo 'trapped SIGCHLD' >$mypts" SIGCHLD
trap "echo 'trapped SIGHUP' >$mypts" SIGHUP
trap "echo 'trapped SIGINT' >$mypts" SIGINT
trap "echo 'trapped SIGPIPE' >$mypts" SIGPIPE
trap "echo 'trapped SIGSEGV' >$mypts" SIGSEGV
trap "echo 'trapped SIGSYS' >$mypts" SIGSYS
trap "echo 'trapped SIGTERM' >$mypts" SIGTERM
function h4 {
# function traps
# these mask the main traps
#trap "echo 'trapped h4 SIGCHLD'" SIGCHLD
#trap "echo 'trapped h4 SIGHUP'" SIGHUP
#trap "echo 'trapped h4 SIGINT'" SIGINT
#trap "echo 'trapped h4 SIGPIPE'" SIGPIPE
#trap "echo 'trapped h4 SIGSEGV'" SIGSEGV
#trap "echo 'trapped h4 SIGSYS'" SIGSYS
#trap "echo 'trapped h4 SIGTERM'" SIGTERM
{
# compound statement traps
# these mask the function traps
#trap "echo 'trapped compound SIGCHLD'" SIGCHLD
#trap "echo 'trapped compound SIGHUP'" SIGHUP
#trap "echo 'trapped compound SIGINT'" SIGINT
#trap "echo 'trapped compound SIGPIPE'" SIGPIPE
#trap "echo 'trapped compound SIGSEGV'" SIGSEGV
#trap "echo 'trapped compound SIGSYS'" SIGSYS
#trap "echo 'trapped compound SIGTERM'" SIGTERM
echo begin err 1>&2
echo begin log
# enable one of sleep/while/find
#sleep 63
#while : ; do sleep 0.1; done
find ~ 2>/dev/null 1>/dev/null
echo end err 1>&2
echo end log
} \
2> >(
trap "echo 'trapped 2 SIGCHLD' >$mypts" SIGCHLD
trap "echo 'trapped 2 SIGHUP' >$mypts" SIGHUP
trap "echo 'trapped 2 SIGINT' >$mypts" SIGINT
trap "echo 'trapped 2 SIGPIPE' >$mypts" SIGPIPE
trap "echo 'trapped 2 SIGSEGV' >$mypts" SIGSEGV
trap "echo 'trapped 2 SIGSYS' >$mypts" SIGSYS
trap "echo 'trapped 2 SIGTERM' >$mypts" SIGTERM
echo begin 2 >$mypts
awk '{ print "processed by 2: " $0 }' >$mypts &
wait
echo end 2 >$mypts
) \
1> >(
trap "echo 'trapped 1 SIGCHLD' >$mypts" SIGCHLD
trap "echo 'trapped 1 SIGHUP' >$mypts" SIGHUP
trap "echo 'trapped 1 SIGINT' >$mypts" SIGINT
trap "echo 'trapped 1 SIGPIPE' >$mypts" SIGPIPE
trap "echo 'trapped 1 SIGSEGV' >$mypts" SIGSEGV
trap "echo 'trapped 1 SIGSYS' >$mypts" SIGSYS
trap "echo 'trapped 1 SIGTERM' >$mypts" SIGTERM
echo begin 1 >$mypts
awk '{ print "processed by 1: " $0 }' >$mypts &
wait
echo end 1 >$mypts
)
echo end fnc
}
h4
echo finish
Чтобы получить дерево процессов ascii-art (в отдельном терминале):
ps axjf | less
---
---
Мне трудно понять, как сигналы распространяются в bash и, следовательно, какая ловушка будет их обрабатывать.
У меня есть 3 примера здесь. Каждый пример был протестирован с 2 вариантами, то есть ни одна строка не была прокомментирована. Примеры построены с помощью этого псевдокода:
main_trap
func
compound_statement(additional_traps) > process_redirection(additional_traps)
Я попробовал каждый пример с обоими вариантами несколько раз. Я получил несколько результатов, я опубликовал результаты, которые я нашел.
Тест был сделан следующим образом:
- поместите скрипт в файл
- запустить файл скрипта
- Нажмите
Ctrl+C
пока скрипт еще работает
ПРИМЕЧАНИЕ. Простое копирование этих скриптов в существующую оболочку bash дает результаты, отличные от тех, которые я получил при выполнении из файла. Чтобы ограничить длину этого вопроса, я не приложил эти результаты.
Мой последний вопрос:
Я использовал этот макет (составной оператор + перенаправление процесса) для запуска некоторого кода, а также для фильтрации и сохранения выходных данных. Теперь по какой-то причине я решил, что было бы лучше защитить эту настройку от прерывания при прерывании, но мне очень трудно это сделать. Вскоре я обнаружил, что простого вызова trap в начале скрипта недостаточно.
Есть ли способ защитить мой скрипт от сигналов (и установить правильную последовательность выключения), используя bash / trap?
Сигналы, как правило, сначала стирают записи, поэтому я не могу уловить умирающие линии основного процесса...
(Я добавляю больше мыслей и анализа в конце вопроса.)
Это будет длинный вопрос, но я решил, что публикация работы, которую я уже сделал, поможет понять, что происходит:
НАСТРОЙКИ ИСПЫТАНИЙ:
НАСТРОЙКА ТЕСТА 1 (1 кошка):
#!/bin/bash
# variation 1:
trap "echo 'trapped' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE
# variation 2:
#trap "echo 'trapped'" SIGTERM SIGINT SIGHUP SIGPIPE
h {
{
echo begin
( trap "echo 'trapped inner' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE;
sleep 63 )
echo end
} \
2> >( trap "echo 'trapped 2' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE;
cat ) \
1> >( trap "echo 'trapped 1' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE;
cat )
echo end 2
}
h
echo finish
Результаты:
# variation 1:
# trap "echo 'trapped' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE
begin
^Ctrapped 2
Segmentation fault
# variation 2:
# trap "echo 'trapped'" SIGTERM SIGINT SIGHUP SIGPIPE
begin
^Cend 2
finish
trapped 2
begin
^Ctrapped 2
end 2
finish
begin
^Ctrapped 2
Segmentation fault
НАСТРОЙКА ТЕСТА 2 (2 кошки):
#!/bin/bash
# variation 1:
trap "echo 'trapped' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE
# variation 2:
#trap "echo 'trapped'" SIGTERM SIGINT SIGHUP SIGPIPE
h2 {
{
echo begin
( trap "echo 'trapped inner' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE;
sleep 63 )
echo end
} \
2> >( trap "echo 'trapped 2' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE;
cat; cat ) \
1> >( trap "echo 'trapped 1' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE;
cat; cat )
echo end 2
}
h2
echo finish
Результаты:
# variation 1:
# trap "echo 'trapped' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE
begin
^Ctrapped 2
end 2
finish
end
trapped 1
trapped
begin
^Ctrapped 2
end 2
finish
end
trapped
begin
^Cend 2
finish
trapped 2
end
trapped inner
trapped
trapped 1
# variation 2:
# trap "echo 'trapped'" SIGTERM SIGINT SIGHUP SIGPIPE
begin
^Ctrapped 2
end 2
finish
trapped inner
trapped 1
trapped
end
begin
^Ctrapped 2
end 2
finish
trapped
end
trapped inner
trapped 1
begin
^Ctrapped 2
end 2
finish
trapped inner
trapped 1
trapped
end
НАСТРОЙКА ТЕСТА 3 (2 кошки, без сна сна):
#!/bin/bash
# variation 1:
trap "echo 'trapped' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE
# variation 2:
#trap "echo 'trapped'" SIGTERM SIGINT SIGHUP SIGPIPE
h3 {
{
echo begin
sleep 63
echo end
} \
2> >( trap "echo 'trapped 2' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE;
cat; cat ) \
1> >( trap "echo 'trapped 1' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE;
cat; cat )
echo end 2
}
h3
echo finish
Результаты:
# variation 1:
# trap "echo 'trapped' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE
begin
^Ctrapped 2
end 2
finish
end
trapped 1
trapped
begin
^Ctrapped 2
end 2
finish
trapped 1
trapped
end
begin
^Cend 2
finish
trapped 2
trapped 1
trapped
end
begin
^Cend 2
finish
end
trapped 2
trapped 1
trapped
begin
^Cend 2
finish
trapped 2
end
trapped
trapped 1
begin
^Cend 2
finish
end
trapped 2
# variation 2:
# trap "echo 'trapped'" SIGTERM SIGINT SIGHUP SIGPIPE
begin
^Cend 2
trapped 2
finish
trapped
end
trapped 1
begin
^Ctrapped 2
end 2
finish
trapped
end
trapped 1
begin
^Ctrapped 2
end 2
finish
trapped 1
trapped
end
МОЙ АНАЛИЗ:
Основная причина, по которой я добавил все 3 тестовых случая, заключается в том, что иногда SEGFAULT
, Я выполнил дамп, но не смог выяснить, откуда он. Кажется, что это в некоторой степени зависит от того, перенаправлено ли эхо-сигнал в основной ловушке на /dev/stderr
(вариант 1) или нет (вариант 2).
Сразу после Ctrl+C
, обычно "trapped 2"
активируется первым, редко "end 2"
, Это говорит о том, что (вопреки моим первоначальным убеждениям) при обработке сигнала не задействована иерархия процессов. Запущенные процессы (составной оператор, 2 замены процесса, в h и h2 подоболочки, sleep
процесс, cat
процессы) выполняются параллельно, и в зависимости от того, что происходит во время доставки сигнала, он будет обрабатываться. По какой-то причине это в основном подстановка процесса перенаправления stderr. Я полагаю cat
является основным приемником, на котором не установлен обработчик сигнала, поэтому он просто умирает (вот почему я экспериментировал с добавлением 2 cat
s, так что второй может поддерживать работу подоболочки).
Это тот момент, когда я понятия не имею, что происходит. (Я даже не знаю, понял ли я это до этого момента...)
Я думаю, сигнал будет распространяться от cat
к содержащему его процессу, оболочке bash замещения процесса, в которой установлен обработчик сигнала, и печатает "trapped 2"
,
Теперь, я бы подумал, история на этом закончится, одно кольцо будет уничтожено Исильдур, Фродо останется дома... Но нет. Каким-то образом это вспыхивает, и удается убить sleep
, также. Даже если есть 2 cat
s, так что если один уничтожен, подоболочка остается в живых. Я обнаружил, что наиболее вероятно, что SIGPIPE
это то, что убивает сон, так как, не поймав этого, я увидел поведение, отличное от того, что я написал здесь. Но что интересно, похоже, что мне нужно trap
SIGPIPE
в каждом месте, а не только в подоболочке сна, или опять же, это демонстрирует различное поведение.
Я думаю, SIGPIPE
сигнал достигает sleep
убивает, так что есть только echo
осталось в составном операторе, который выполняется, и эта подоболочка завершена. Подстановка процесса перенаправления stdout также прекращается, вероятно, другим SIGPIPE
убитым составным оператором / функцией оболочки?
Еще более интересно, иногда "trapped 1"
вообще не показывается.
Странно, что я не вижу 50% "trapped 2"
и 50% "trapped 1"
,
Могу ли я сделать, что я хочу с этим?
Имейте в виду, моя цель - упорядоченное отключение системы / службы / скрипта.
1) Прежде всего, как я вижу, если " бизнес-процессы ", представленные здесь sleep
/ cat
не имеют своей собственной обработки сигналов, никакого количества trap
может спасти их от гибели.
2) Обработчики сигналов не наследуются, каждый подоболочка должна иметь свою собственную систему ловушек.
3) Нет ничего лучше, чем группа процессов, которая будет обрабатывать сигнал общедоступным образом, какой бы процесс ни произошел с ударом, он сделает свое дело, а результаты уничтоженных там процессов могут распространяться дальше в дереве процессов.
Для меня не ясно, однако, если процесс не может обработать сигнал, он бросит его в содержащую его оболочку? Или это еще один сигнал, что доставляется? Что-то, безусловно, проходит, иначе обработчики сигналов не сработают.
В / моем идеальном мире, trap
будет защищать что-либо в оболочке, где он установлен, от получения сигнала, поэтому sleep
-s, cat
-s будет отключен с помощью назначенной функции очистки: убить sleep
, а остальные будут записывать свои последние строки, а затем - в отличие от: все журналы удаляются, и только после этого основной процесс в конечном итоге будет уничтожен...
Я что-то упускаю тривиально? установить -о магии? Просто продолжайте добавлять больше ловушек, пока они вдруг не сработают
ВОПРОСЫ:
Как на самом деле распространяется сигнал после Ctrl+C
?
Где SEGFAULT
родом из?
Самое важное:
Могу ли я защитить эту структуру от разрушения сигналом, начиная с регистрации? Или я должен избегать подмены процессов и придумывать другой тип фильтрации / регистрации выходных данных?
Протестировано с:
GNU bash, версия 4.4.12(1)-релиз (x86_64-pc-linux-gnu)
Дальнейшие заметки:
После того, как я закончил свои тесты, я нашел эти QA, которые, я думаю, могут быть связаны с моим делом, но я не знаю, как именно я мог их использовать:
Как надежно использовать ловушку, используя Bash, выполняющий дочерние процессы переднего плана
Сигнал ловушки в дочернем фоновом процессе
Тем не менее, я попытался заменить sleep 63
с while : ; do sleep 0.1; done
Вот результаты:
НАСТРОЙКА ТЕСТА 1:
# (both variations)
# 1 Ctrl + C got me a SEGFAULT
begin
^Ctrapped 2
Segmentation fault
# 2 Ctrl + C got me a SEGFAULT
begin
^Ctrapped 2
^CSegmentation fault
НАСТРОЙКА ТЕСТА 2:
# variation 1
# trap "echo 'trapped' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE
begin
^Ctrapped 2
trapped 1
trapped inner
^Ctrapped 2
^CSegmentation fault
# variation 2
# trap "echo 'trapped'" SIGTERM SIGINT SIGHUP SIGPIPE
begin
^Ctrapped 2
trapped inner
trapped 1
^Ctrapped 2
Segmentation fault
begin
^Ctrapped 2
trapped inner
trapped 1
^Ctrapped 2
^CSegmentation fault
НАСТРОЙКА ТЕСТА 3:
# variation 1
# trap "echo 'trapped' >/dev/stderr" SIGTERM SIGINT SIGHUP SIGPIPE
begin
^Ctrapped 2
trapped 1
trapped
^Ctrapped 2
^CSegmentation fault
# variation 2
# trap "echo 'trapped'" SIGTERM SIGINT SIGHUP SIGPIPE
begin
^Ctrapped 2
trapped 1
trapped
^Ctrapped 2
^CSegmentation fault
^Ctrapped 2
trapped 1
trapped
^Ctrapped 2
Segmentation fault
Таким образом, хотя это позволило мне извлечь выгоду из использования 2 cat
с учетом 2 Ctrl+C
-я, это неизменно меня достало SEGFAULT
, до сих пор не знаю, откуда это взялось.
1 ответ
После бесчисленных экспериментов я пришел к выводу, что невозможно делать то, что я хочу, но я до сих пор не понимаю всех деталей.
Я публикую свои результаты, но некоторое время не принимаю мой ответ, в случае, если - в надежде - кто-то лучше понимает, что происходит.
Кажется, я неправильно понял несколько вещей...
1) SEGFAULT
приходит от записи на закрытый fd (stderr
). Однако я думаю, что это происходит где-то глубоко внутри bash или даже на уровне ядра, возможно, в каком-то состоянии гонки - я бы предположил, что дерево процессов, управляемое bash, получило бы сегрегацию на оставшемся адресе виртуальной памяти закрытого ввода-вывода (Я подозреваю, это вызывает ошибку). Во всяком случае, замена /dev/stderr
с правильным устройством TTY, кажется, решить эту проблему.
Записать в терминал после перенаправления stdout в файл без использования stderr?
echo или print /dev/stdin /dev/stdout /dev/stderr
2) Вся проблема с остановкой журналов перед записанным процессом связана с тем, что все они находятся в группе процессов переднего плана. На Ctrl+C
Терминал доставит SIGINT
каждому процессу в группе процессов fg. Как выясняется после печати дерева процессов, процессы регистратора являются первыми в напечатанном массиве, поэтому, вероятно, они доставляются первыми и обрабатывают SIGINT
,
Как Ctrl-C завершает дочерний процесс?
Как заставить программу чтения стандартного запуска работать в фоновом режиме на Linux?
Контроль, какой процесс отменяется с помощью Ctrl + C
3) Оболочка, которая порождает процессы, не контролирует доставку сигнала, фактически она ожидает, поэтому в этой оболочке невозможно установить какое-то волшебство для защиты чего-то вроде cat
запускается оболочкой, в которой не установлен обработчик сигнала.
4) Видя, что проблема возникает из-за всех процессов, входящих в группу процессов fg, кажется очевидным, что перемещение ненужных процессов в фоновый режим было бы решением, например:
2> >( cat & )
К сожалению, в этом случае выходные данные не доставляются cat
вместо этого он заканчивается мгновенно.
Я подозреваю, что это имеет какое-то отношение к фоновой работе, получающей SIGSTOP
, если это stdin
открыт в то время, когда он находится на заднем плане.
Запись в stdin фонового процесса
Процесс Linux в фоновом режиме - "остановлен" на рабочих местах?
Почему SIGINT не распространяется на дочерний процесс при отправке его родительскому процессу?
НОТА: setsid cmd
сделаю cmd
начать свою собственную сессию, которая будет иметь совершенно новую группу процессов, которая будет содержать cmd
в одиночку, так что, вероятно, он может быть использован для разделения регистратора и журнала. Я не думал об этом и не экспериментировал с этим.
Запуск процесса в фоновом режиме с перенаправлением ввода / вывода
Дальнейшие ссылки:
Отправить команду в фоновый процесс
Почему Bash такой: распространение сигналов
Как распространить SIGTERM на дочерний процесс в скрипте Bash
Заключение
В настройках:
{
cmd
} \
2> >(logger) \
1> >(logger)
Я не нашел хорошего способа отделить cmd
от logger
на уровне группы процессов. Фоновая logger
s отключает их от получения вывода, вместо этого они немедленно завершаются, вероятно, через SIGSTOP
,
Одним из решений может быть использование именованных каналов, которые позволят получить больший контроль, а также возможность отсоединить зарегистрированные процессы и процессы ведения журнала. Тем не менее, я изначально решил использовать замену процесса, предоставляемую bash, чтобы избежать дополнительной сложности кодирования каналов вручную.
Способ, который я выбрал в конце, состоит в том, чтобы просто создать фоновое дерево всего процесса (cmd
+ logger
s), и пусть другой уровень разбирается с сигналами.
f {
{
cmd
} \
2> >(logger) \
1> >(logger)
}
trap ...
set -m
f &
wait
ОБНОВИТЬ:
Я понял, что простого фонового изображения недостаточно, поскольку неинтерактивная оболочка (запуск сценария из файла) НЕ запускает фоновые процессы в отдельной группе процессов. Для этого проще всего установить оболочку в интерактивный режим: set -m
, (Я надеюсь, что это не вызовет новых проблем, пока что кажется хорошим.)
НОТА: setsid
не работает с функциями, поэтому основному сценарию потребуется собственный файл, и он запускается из второго файла сценария.
Запрет SIGINT прерывать вызов функции и дочерний процесс (ы) внутри