У меня есть сценарий-оболочка Bash, который запускает сложный сценарий моделирования, который, в свою очередь, запускает несколько подпроцессов и собственных сценариев. Я хочу выяснить, как отслеживать все процессы, порождаемые одним запуском сценария моделирования, чтобы уничтожать их все при выполнении определенных критериев.
Например, мой скрипт-оболочка под названием pipeline_runner.sh
делает следующее:
#!/bin/bash
# Some set up of the script ...
./monitor_job.sh ... arguments TBD ... &
script_path = "path/to/bash/script"
chmod u+x "$script_path"
"$script_path"
# ...
Каждый запуск pipeline_runner.sh
запускает экземпляр monitor_job.sh
в фоновом режиме для отслеживания конкретного запуска path/to/bash/script
, запущенного этим запуском pipeline_runner.sh
. Когда какое-то произвольное условие, определенное в monitor_job.sh
, выполняется, оно должно иметь возможность уничтожить этот конкретный запуск path/to/bash/script
вместе со всеми процессами, прямо или косвенно запущенными им.
Множество других процессов, запускаемых запуском path/to/bash/script
, многочисленны и различаются, поэтому я пытаюсь выяснить, как захватить каждый скрипт, который создается в результате запуска, в какую-то группу или список и иметь возможность уничтожить их все, когда это необходимо. Уничтожить только начальный процесс $script_path
недостаточно, поскольку все подпроцессы этого сценария сохранятся.
Важными второстепенными целями являются:
сделать эту динамику такой, чтобы она не зависела от того, какой сценарий обозначен $script_path
. Это означает, что я не могу просто жестко закодировать конкретные имена команд для поиска.
осуществлять мониторинг в отдельном скрипте (monitor_job.sh
), как описано, а не напрямую в pipeline_runner.sh
.
Как я могу отслеживать все процессы, запускаемые скриптом моделирования, чтобы иметь возможность убить их все при необходимости?
monitor_job.sh
также должен запустить $script_path
или, по крайней мере, pipeline_runner.sh
должен запуститься сначала script_path
, а затем передать идентификатор процесса (доступный из $PID
) в качестве аргумента monitor_job.sh
. Идентификатор процесса не так надежен, поскольку всегда существует вероятность того, что процесс может завершиться и новый процесс получит идентификатор переработанного процесса, а вы не заметите. Однако родительский процесс всегда получает уведомление непосредственно при выходе одного из его дочерних процессов.
Раз уж вы отметили Linux, я склоняюсь к Linux cgroups. SystemD использует эту возможность практически для того, что вы описываете. Однако я недостаточно авторитетен в CGroups, чтобы рекомендовать детали.
На самом деле, хотя это не совсем программный ответ, вы можете добиться того, чего хотите, используя сам SystemD, запустив свой pipeline_runner.sh
в качестве сервисной единицы пользовательского уровня.
Вероятно, вы захотите посмотреть идентификатор родительского процесса (PPID), чтобы выяснить, из чего он был запущен. Каждый раз, когда мне хотелось уничтожить группу процессов, порожденных сеансом Bash, я просто использовал grep
для поиска PID оболочки в выводе ps
, показывающего поле PPID. Все подпроцессы обычно используют один и тот же PPID.
kill -- -$$
завершит всю группу процессов.
Например, в следующем скрипте мы запускаем 2 подпроцесса, sleep 15
и sleep 30
, тогда у нас могут быть другие задачи для запуска (в данном случае sleep 5
), и поскольку мы удовлетворяем нашим критериям выхода, мы можем убить всю группу процессов.
#!/bin/sh
echo "Parent pid $$"
sleep 15 &
echo "child 1 pid $!"
sleep 30 &
echo "child 2 pid $!"
sleep 5
echo "criteria met"
kill -- -$$
Если мы запустим это с bash test.sh ; ps -ef | grep sleep
, мы получим:
$ bash test.sh ; ps -ef | grep sleep
Parent pid 87546
child 1 pid 87547
child 2 pid 87548
criteria met
Terminated: 15
501 87595 789 0 2:08PM ttys007 0:00.00 grep sleep
Таким образом, мы видим, что подпроцессы также были убиты.
Проблема с этим подходом в том, что если бы сочетание клавиш Ctrl+C было введено сразу после моего выполнения, мы бы получили:
$ bash test.sh ; ps -ef | grep sleep
Parent pid 88352
child 1 pid 88353
child 2 pid 88354
^C
501 88353 1 0 2:10PM ttys007 0:00.00 sleep 15
501 88354 1 0 2:10PM ttys007 0:00.00 sleep 30
501 88391 789 0 2:10PM ttys007 0:00.00 grep sleep
Это означает, что различные подпроцессы будут продолжать работать как «сироты» (принятые процессом инициализации).
Чтобы решить эту проблему, мы могли бы использовать trap
и изменить наш скрипт на:
#!/bin/sh
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
echo "Parent pid $$"
sleep 15 &
echo "child 1 pid $!"
sleep 30 &
echo "child 2 pid $!"
sleep 5
echo "criteria met"
exit 0
Обычное выполнение останется прежним, потому что exit 0
будет пойман ловушкой и, в свою очередь, kill -- -$$
будет выполнен, как и раньше.
Теперь, если мы запустим наш скрипт и введем ctrl+c сразу после выполнения, на этот раз мы получим:
Parent pid 91578
child 1 pid 91579
child 2 pid 91580
^CTerminated: 15
501 91590 789 0 2:18PM ttys007 0:00.00 grep sleep
где мы видим, что подпроцесс также был убит.
Фантастика! Большое спасибо за мастер-класс! Это именно то, что я ищу.
Я не уверен, как бы вы сделали это из сценария оболочки, но если вы можете запустить
pipeline_runner.sh
в отдельной группе процессов, вы можете уничтожить всю pgroup.