Как использовать Ctrl+C для завершения всех фоновых процессов, запущенных в Bash-скрипте?

Вопрос или проблема

ArchLinux (Manjaro).

Я запускаю один bash файл. Он запускает 2 процесса (команды), используя &. Но когда я нажимаю Ctrl+C, чтобы остановить программу – один процесс умирает, а другой продолжает работать.

Как мне остановить оба процесса? Или как мне написать новый скрипт для завершения этих двух процессов?

Обновление: trap требует удаления префикса SIG в условии, хотя некоторые оболочки поддерживают его использование. См. комментарий ниже.

Амперсанд “&” запускает команду в фоновом режиме в новом процессе. Когда ее родительский процесс (команда, запускающая bash скрипт в вашем случае) завершается, этот фоновый процесс сбрасывает своего родителя на init (процесс с PID 1), но не умирает. Когда вы нажимаете ctrl+c, вы отправляете сигнал прерывания процессу на переднем плане, и это не повлияет на фоновый процесс.

Чтобы завершить фоновый процесс, вы должны использовать команду kill с PID самого последнего фонового процесса, который можно получить с помощью $!.

Если вы хотите использовать ctrl+c для завершения как скрипта, так и фонового процесса, вы можете сделать следующее:

trap 'kill $BGPID; exit' INT
sleep 1024 &    # фоновая команда
BGPID=$!
sleep 1024      # команда скрипта на переднем плане

trap изменяет обработчик сигнала SIGINT ( trap требует удаления префикса SIG, но некоторые оболочки могут поддерживать его использование), поэтому скрипт завершает процесс с $BGPID перед выходом.

Программы могут игнорировать сигнал Ctrl+c, так же как они могут игнорировать SIGTSTP.

Вы можете попробовать Ctrl+z в большинстве оболочек (не всегда, но чаще всего)

Существуют некоторые сигналы, которые не могут быть проигнорированы процессом: SIGKILL, SIGSTOP. Вы можете отправить команду kill. Чтобы убить ваш зависший процесс, вам нужно найти идентификатор процесса (PID).
используйте pgrep или ps, а затем убейте его

 % kill -9 PID

Для нескольких фоновых процессов

#! /bin/bash -e

# Убедитесь, что вы `kill` каждый из этих процессов по отдельности,
# если остановите выполнение на полпути, так как они просто продолжат работать в противном случае
trap 'for pid in ${main_pids[*]}; do kill $pid; done; exit' INT

main_pids=()
for i in "${!STATES_SETS[@]}"; do
  time python script.py &
  main_pids[${i}]=$!
done

# Ждем завершения всех pids, чтобы убедиться, что все в порядке
for pid in ${main_pids[*]}; do
  wait $pid
done

В VSCode, похоже, если вы пытаетесь просмотреть идентификатор процесса, он дает вам какой-то странный идентификатор, который не совпадает с тем, что вы можете увидеть с помощью ps. Тем не менее, код будет работать. Можно проверить с помощью: ps -aux | grep script.py

Ответ или решение

Чтобы эффективно завершить все фоновые процессы, запущенные в Bash-скрипте с использованием комбинации клавиш Ctrl+C, вам необходимо правильно обработать сигнал прерывания (SIGINT) с помощью команды trap. Ниже приведены шаги и пример скрипта, который демонстрирует, как это сделать.

Основная проблема

Когда вы запускаете команду в фоновом режиме с помощью символа &, она работает в отдельном процессе. Когда вы нажимаете Ctrl+C, прерывается только главный процесс скрипта, и фоновые процессы продолжают выполнение. Чтобы завершить оба, вам нужно предписать скрипту отлавливать сигнал прерывания и завершать все созданные фоновые процессы.

Пример скрипта

#! /bin/bash -e

# Инициализируем массив для хранения PID фоновых процессов
declare -a main_pids

# Устанавливаем обработчик сигнала для SIGINT
trap 'for pid in "${main_pids[@]}"; do kill $pid; done; exit' INT

# Запускаем фоновые процессы и сохраняем их PID
for i in {1..2}; do
  sleep 60 &     # Замените это на ваш команду
  main_pids[$i]=$!
done

# Ожидаем завершения всех фоновых процессов
for pid in "${main_pids[@]}"; do
  wait $pid
done

Объяснение кода

  1. Объявление массива main_pids: Этот массив будет использоваться для хранения идентификаторов процессов (PID) фоновых задач.

  2. Обработчик сигнала trap: С помощью команды trap мы указываем, что при получении сигнала SIGINT (Ctrl+C) необходимо выполнить цикл, который завершает каждый PID из массива main_pids, после чего происходит выход из скрипта.

  3. Запуск фоновых процессов: В данном примере мы используем sleep 60 & как запущенную команду. Вы можете заменить sleep 60 на любой другой процесс, который хотите запустить в фоновом режиме.

  4. Ожидание завершения процессов: Включает цикл wait, который будет дожидаться завершения всех фоновых процессов, используя их BGPID.

Примечания

  • Сигнал SIGINT может быть игнорирован некоторыми программами. Если это произойдет, вы можете использовать более агрессивные методы завершения, такие как kill -9 PID, но будьте осторожны с этой командой, так как она может принудительно завершить процессы, не давая им возможности корректно завершиться.
  • Для завершения нескольких фоновых процессов можно использовать массив, как показано в примере, чтобы управлять их PID централизованно.

Следуя приведенной инструкции, вы сможете успешно завершить все фоновые процессы, запущенные в вашем Bash-скрипте, при нажатии комбинации клавиш Ctrl+C.

Оцените материал
Добавить комментарий

Капча загружается...