Краткий ответ
Когда оболочка обрабатывает сигнал с помощью trap
, любой ожидающий в данный момент read
завершится ошибкой. Вот почему сценарий завершается преждевременно.
Чтобы исправить это, установите флаг в обработчике. Затем, когда read
не удается, проверьте флаг. Если флаг установлен, то вы знаете, что read
не удалось из-за сигнала, а не конца файла, поэтому вам следует продолжить выполнение l oop.
Что происходит?
Во-первых, давайте немного упростим сценарий, а также добавим несколько распечаток, чтобы увидеть, что происходит. Вот trap-fifo1.sh
:
#!/bin/sh
echo "my PID: $$"
trap handler USR1
handler() {
echo "in 'handler'"
echo caught_user1 > fifo
echo "handler complete"
}
out() {
echo "in 'out'"
while read -r line; do
echo $line
done
echo "loop terminated"
}
echo "about to invoke 'out'"
out < fifo
echo "at end of script"
Чтобы увидеть манифест проблемы, я собираюсь запустить три команды на трех разных терминалах. Каждый терминальный сеанс представлен столбцом, а вертикальная ось является глобальным временем:
Terminal 1 Terminal 2 Terminal 3
--------------- ------------- ----------------
$ ./trap-fifo1.sh
my PID: 42436
about to invoke 'out'
$ cat > fifo
in 'out'
hi
hi
$ kill -s SIGUSR1 42436
in 'handler'
handler complete
loop terminated
at end of script
Значение l oop остановилось, когда мы отправили SIGUSR1
. Это связано с тем, что ожидающий read
сбой при получении и обработке сигнала. (Помимо: я изо всех сил пытался найти авторитетную ссылку на это эмпирически наблюдаемое поведение. Лучшее, что я нашел, это https://ss64.com/bash/trap.html, но я не знаю, откуда взялся этот текст. Я не смог найти его ни в одном из них POSIX ни bash руководство .)
Как мы можем это исправить?
Проблема заключается в неоднозначности в read
, что мы не знаю, если это происходит из-за trap
или из-за конца файла. Поэтому мы установим флаг в handler
. Вот trap-fifo2.sh
:
#!/bin/sh
echo "my PID: $$"
trap handler USR1
handler_invoked=false
handler() {
echo "in 'handler'"
echo caught_user1 > fifo
handler_invoked=true
echo "handler complete"
}
out() {
echo "in 'out'"
while true; do
if read -r line; then
echo $line
elif $handler_invoked; then
echo "flag set, continuing"
handler_invoked=false
else
echo "flag unset, stopping"
break
fi
done
echo "after while loop"
}
echo "about to invoke 'out'"
out < fifo
echo "at end of script"
Теперь давайте посмотрим на исправление в действии:
Terminal 1 Terminal 2 Terminal 3
--------------- ------------- ----------------
$ ./trap-fifo2.sh
my PID: 42562
about to invoke 'out'
$ cat > fifo
in 'out'
hello
hello
$ kill -s SIGUSR1 42562
in 'handler'
handler complete
flag set, continuing
caught_user1
again
again
$ kill -s SIGUSR1 42562
in 'handler'
handler complete
flag set, continuing
caught_user1
(Ctrl+D)
flag unset, stopping
after while loop
at end of script
Оттуда нам нужно только адаптировать оригинальный скрипт, добавив флаг и отметив его в L oop, который должен быть простым.