Минимальный скрипт test_pipe_pid
:
#!/bin/bash
echo "started PID=$$"
trap 'echo killed PID=$$; exit 1' INT
yes | while read line; do
trap 'echo killed inner PID=$$; exit 1' INT
jobs -p
sleep 1
done
Запускаем и пытаемся убить по основному PID-у:
~$ ./test_pipe_pid &
[1] 2320
started PID=2320
~$ pstree -p 2320
test_pipe_pid(2320)─┬─test_pipe_pid(2326)───sleep(2563)
└─yes(2324)
~$ kill -INT 2320
# А он живой:
~$ pstree -p 2320
test_pipe_pid(2320)─┬─test_pipe_pid(2326)───sleep(3021)
└─yes(2324)
~$ kill -INT 2326
# И только теперь умер:
~$ killed inner PID=2320
killed PID=2320
[1]+ Выход 1 ./test_pipe_pid
Откуда берётся дополнительный процесс? Его не видно в jobs -p
, и он перехватывает обработку сигналов. Как тогда, зная родительский pid, прервать весь скрипт?
При запуске в foreground и посыле Ctrl-C напрямую скрипт убивается целиком, с потрохами. А в чём тогда принципиальная разница?
Как это гуглить? Гугл не знает, что такое «bash pass signal to subshell» в любых комбинациях. В мане есть только заметка «Each command in a pipeline is executed as a separate process (i.e., in a subshell).» Нашёл рецепт делать trap 'kill 0' INT
, но ничего не изменилось, сабшелл продолжает жить, пока его не убьёшь прямо.
Зачем это нужно? Есть некий процесс, который запускает разные команды, в т.ч. и такой скрипт. По определённому запросу извне процесс должен закрывать все запущенные команды. Остальные команды закрываются по SIGINT как ожидается, только такой Маклауд с пайпами остаётся.