Вопрос или проблема
У меня есть оболочный скрипт, который запускает python.
start.sh
# Настройка окружения
cd ~/foobar/ && \
python3 -m venv env && \
source env/bin/activate && \
pip install -q -r requirements.txt && \
find . -maxdepth 1 -type f -name "*.py" | xargs pylint
# Запуск скриптов
python ~/foobar/main.py
Если я нажимаю ctrl+c
в терминале, python завершается корректно. Однако если я использую pkill -9 start.sh
, появится сообщение Killed
, но python всё ещё будет работать. Есть ли способ завершить скрипт python, когда вызывающий скрипт прекращает работу?
Интересный вопрос: что должно завершить скрипт Python? Если вы уничтожите процесс bash с помощью SIGKILL
, то он определенно ничего не сделает с скриптом Python.
Вы можете запустить фоновый процесс, который регулярно проверяет, существует ли основной процесс bash, и завершает скрипт Python, если его больше нет.
Если вы можете изменить скрипт Python, он может регулярно проверять, ушел ли его родитель (легче всего: проверьте, если PID родителя равен 1) и завершить себя. Таким образом, случайное завершение проверяющего не приведет к проблемам…
Лучшее решение может быть вне обоих скриптов. Вы можете запустить оболочный скрипт как сервис SystemD (обычный или временный, см. man systemd-run
) и использовать systemctl
для завершения скрипта (т.е. завершения всей cgroup).
Используйте только скрипт python и управляйте им с помощью unit systemd (как сервис)
Ответ или решение
Как завершить процесс Python, запущенный скриптом оболочки, при его завершении?
Если вы столкнулись с проблемой завершения процесса Python, который запущен через скрипт оболочки, и этот процесс продолжает исполняться, даже если сам скрипт был убит (например, с использованием pkill -9 start.sh
), то вам стоит рассмотреть несколько методов для решения этой задачи. Ниже приведены рекомендации, которые помогут корректно завершаать процесс Python вместе с его родительским скриптом.
1. Использование сигналов и возможностей оболочки
Когда pkill -9
выполняется, сигнал SIGKILL
отправляется, что приводит к немедленному завершению процесса оболочки. Однако при этом дочерние процессы не могут быть завершены. Чтобы гарантировать завершение дочернего процесса Python, необходимо использовать более контролируемые сигналы.
Использование trap
:
Вы можете использовать команду trap
в вашем start.sh
для обработки сигналов и завершения дочерних процессов. Вот пример:
#!/bin/bash
# Установка обработки сигналов
trap 'kill $(jobs -p)' EXIT
# Настройка окружения
cd ~/foobar/ && \
python3 -m venv env && \
source env/bin/activate && \
pip install -q -r requirements.txt && \
find . -maxdepth 1 -type f -name "*.py" | xargs pylint
# Запуск скрипта
python ~/foobar/main.py &
wait
В этом примере trap 'kill $(jobs -p)' EXIT
ловит выходные сигналы и завершает все дочерние процессы (включая Python), когда скрипт оболочки завершает свою работу.
2. Создание функции для мониторинга родительского процесса
Если вы можете отредактировать свой Python-скрипт, то добавьте функцию, которая будет проверять, существует ли родительский процесс. Если он не существует, процесс Python завершает свою работу:
import os
import sys
import time
def parent_alive():
try:
while True:
parent_pid = os.getppid()
if parent_pid == 1: # Если PID родителя стал 1, значит, процесс завершен
print("Parent process terminated. Exiting...")
sys.exit()
time.sleep(1) # Проверяем каждый 1 секунду
except KeyboardInterrupt:
print("Terminating...")
if __name__ == "__main__":
# Запуск функции в отдельном потоке
import threading
threading.Thread(target=parent_alive, daemon=True).start()
# Основная логика вашего скрипта
print("Running main process...")
# Например, имитация длинной задачи
while True:
time.sleep(1)
3. Использование systemd для управления процессами
Если вы ориентированы на производственную среду, рассмотрите возможность управления вашим скриптом через systemd
. Это позволит вам запускать ваш скрипт как службу, что обеспечит облегчённое управление процессами. В случае завершения родительского процесса (службы), все дочерние процессы также будут автоматически завершены.
Создайте файл конфигурации для systemd
(например, /etc/systemd/system/my-python-script.service
):
[Unit]
Description=My Python Script
[Service]
ExecStart=/path/to/start.sh
WorkingDirectory=/home/user/foobar/
StandardOutput=journal
StandardError=journal
Restart=on-failure
[Install]
WantedBy=multi-user.target
Затем активируйте и запустите службу:
sudo systemctl enable my-python-script.service
sudo systemctl start my-python-script.service
Заключение
Использование этих подходов позволит вам надёжно завершать процессы Python, запущенные вашими оболочными скриптами, в случае их аварийного завершения. Выбор наиболее подходящего подхода зависит от ваших предпочтений и специфики применения. Важно помнить, что использование SIGKILL
не обеспечивает корректного закрытия всех дочерних процессов, и лучшей практикой будет применение сигналов, позволяющих процессам корректно завершаться.