Гид по технологиям

Ловля сигналов в Bash и корректное завершение скриптов

8 min read Linux Обновлено 21 Dec 2025
Ловля сигналов в Bash и корректное завершение
Ловля сигналов в Bash и корректное завершение

Ноутбук с Linux и командной строкой Bash

Быстрые ссылки

  • Сигналы и процессы
  • Список сигналов
  • Сигналы в командной строке
  • Ловля сигналов в скриптах
  • Обработка сигналов в скриптах
  • Тестирование и отладка

Сигналы и процессы

Определение: сигнал — это краткое однонаправленное сообщение от ядра или процесса к другому процессу, уведомляющее о событии, требующем реакции.

Сигналы используются для информирования процесса о том, что произошло: пользователь нажал Ctrl+C, закрылась сессия SSH, приложение попыталось обратиться к запрещённой памяти и т.д. Если автор процесса ожидал определённый сигнал, он может написать обработчик — функцию, которая будет вызвана при получении этого сигнала.

Ключевая идея для сценариев: ловите сигналы, которые требуют аккуратного завершения (очистка временных файлов, закрытие сетевых соединений, завершение фоновых задач). Без обработчика процесс может мгновенно завершиться и оставить систему в неоднозначном состоянии.

Важно: не все сигналы можно перехватить. Например, SIGKILL и SIGSTOP не поддаются перехвату или игнорированию.

Список сигналов

Команда trap используется не только для установки обработчиков — с опцией -l она показывает все сигналы, используемые в системе:

trap -l

Вывод trap -l в Ubuntu, показывающий список сигналов

Замечания по реализации:

  • Нумерация сигналов на Linux может достигать 64, но не все номера последовательны: 32 и 33 обычно не реализованы в Linux и зарезервированы для внутреннего использования/реалтайм-потоков. Реалтайм‑сигналы обычно идут от SIGRTMIN до SIGRTMAX.
  • На других Unix‑платформах (например, OpenIndiana) набор и номера сигналов могут отличаться.

Вывод trap -l в OpenIndiana, показывающий расширенный список сигналов

Обращение к сигналам может быть по имени (SIGTERM), по сокращённому имени без префикса SIG (TERM) или по номеру (15).

Категории воздействия сигналов:

  • Terminate — сигнал завершения процесса (может быть перехвачен, если не SIGKILL).
  • Ignore — информационный сигнал, который можно игнорировать.
  • Core — приводит к дампу памяти (core dump).
  • Stop — приостанавливает процесс (пауза), не завершает.
  • Continue — возобновляет выполнение остановленного процесса.

Часто используемые сигналы:

  • SIGHUP (1): сессия/подключение оборвано; часто используется демонами для перечитывания конфигурации.
  • SIGINT (2): пользователь нажал Ctrl+C; прерывание.
  • SIGQUIT (3): Ctrl+\ или Ctrl+D в некоторых терминалах — завершение с дампом.
  • SIGFPE (8): ошибка арифметики, например деление на ноль.
  • SIGKILL (9): немедленное убийство процесса — нельзя перехватить.
  • SIGTERM (15): корректное завершение, можно перехватить и выполнить очистку.

Сигналы на командной строке

Вы можете установить ловушку прямо в оболочке. Пример: ловим SIGINT и выводим сообщение при получении Ctrl+C.

trap 'echo -e "\nCtrl+C обнаружен."' SIGINT

После этого при нажатии Ctrl+C в текущей оболочке будет печататься сообщение вместо немедленного завершения (если команда внутри оболочки поддерживает перехват).

Проверка установленного trap:

trap -p SIGINT

Сброс обработчика к состоянию по умолчанию:

trap - SIGINT

Если trap -p не выводит ничего, обработчик для сигнала не установлен.

Важно: применение trap в интерактивной оболочке отличается от использования в скриптах — будьте внимательны при тестировании.

Ловля сигналов в скриптах

Ниже — практические примеры. Код максимально сохранён от оригинала и корректно работает в bash.

Пример 1. Простой цикл, ловящий SIGINT, SIGQUIT и SIGTERM:

#!/bin/bash

trap "echo I was SIGINT terminated; exit" SIGINT

trap "echo I was SIGQUIT terminated; exit" SIGQUIT

trap "echo I was SIGTERM terminated; exit" SIGTERM

echo $$

counter=0

while true

do

echo "Loop number:" $((++counter))

sleep 1

done

Сохраните как simple-loop.sh и сделайте исполняемым:

chmod +x simple-loop.sh

Запустите и проверьте реакцию на Ctrl+C или отправку сигнала из другого терминала:

./simple-loop.sh
kill -SIGQUIT 
kill -SIGTERM 

Пример 2. Функция для аккуратной остановки и удаления временного файла:

#!/bin/bash

trap graceful_shutdown SIGINT SIGQUIT SIGTERM

graceful_shutdown()
{
    echo -e "\nRemoving temporary file:" $temp_file
    rm -rf "$temp_file"
    exit
}

temp_file=$(mktemp -p /tmp tmp.XXXXXXXXXX)

echo "Created temp file:" $temp_file

counter=0

while true

do
    echo "Loop number:" $((++counter))
    sleep 1
done

Сохраните как grace.sh и сделайте исполняемым:

chmod +x grace.sh

Этот шаблон даёт стандартную стратегию: собрать ресурсы в переменные, поставить trap на начало, затем в обработчике — выполнить чистку и exit.

Пример 3. Разные обработчики и EXIT‑handler:

#!/bin/bash

trap sigint_handler SIGINT
trap sigusr1_handler SIGUSR1
trap exit_handler EXIT

function sigint_handler() {
    ((++sigint_count))
    echo -e "\nSIGINT received $sigint_count time(s)."
    if [[ "$sigint_count" -eq 3 ]]; then
        echo "Starting close-down."
        loop_flag=1
    fi
}

function sigusr1_handler() {
    echo "SIGUSR1 sent and received $((++sigusr1_count)) time(s)."
}

function exit_handler() {
    echo "Exit handler: Script is closing down..."
}

echo $$

sigusr1_count=0
sigint_count=0
loop_flag=0

while [[ $loop_flag -eq 0 ]]; do
    kill -SIGUSR1 $$
    sleep 1
done

Этот пример демонстрирует:

  • использование SIGUSR1/2 для пользовательских сигналов;
  • обработку EXIT, который вызывается при нормальном завершении (кроме ситуаций с SIGKILL);
  • накопительную логику (закрытие после 3 нажатий Ctrl+C).

Обработка сигналов — лучшие практики

  1. Минимизируйте действия в критической секции обработчика. Выполняйте только ту очистку, которая необходима немедленно; сложные долгие операции лучше сигнализировать через флаг и выполнить в основном цикле.
  2. Ставьте trap в начале скрипта, до создания ресурсов, чтобы не пропустить сигнал на старте.
  3. Используйте один общий exit‑handler для завершающей очистки и логирования.
  4. Не полагайтесь на перехват SIGKILL и SIGSTOP — их нельзя обработать.
  5. Делайте обработчики идемпотентными — повторный вызов не должен ломать логику.
  6. Если скрипт запускает фоновые процессы, перехватывайте сигналы и корректно завершайте дочерние процессы (kill – -$PID для группы процессов).

Важно: rm -rf в обработчике должен использовать заранее проверенный путь (не удаляйте непроверенные переменные).

Проверка и отладка

Полезные шаги для тестирования:

  • Вывод PID в начале скрипта: echo $$ — чтобы отправлять сигналы извне.
  • trap -p — проверка установленных ловушек.
  • kill -l — показать список сигналов и их номера.
  • Используйте set -x для трассировки исполнения скрипта.
  • Тестируйте сценарии: нормальное завершение, повторный сигнал во время работы handler, получение сигнала при создании временных ресурсов.

Пример сценариев тестирования (тест‑кейсы):

  • Скрипт создаёт временный файл и при SIGTERM удаляет его.
  • Скрипт стартует фоновые процессы; при SIGINT они корректно завершаются.
  • Обработчик вызывает exit только после успешной очистки (проверить состояние файлов/портов).

Критерии приёмки

  • Скрипт не оставляет временных файлов/блокировок после полученного SIGTERM или SIGINT.
  • Скрипт корректно завершает или пересоздаёт сетевые порты/слоты.
  • Обработчики устойчивы к многократным вызовам.

Шаблонная методология для добавления обработки сигналов

  1. Идентифицируйте ресурсы, требующие очистки: временные файлы, сокеты, PID‑файлы, портфорвардинг.
  2. Добавьте переменные для всех ресурсов (вверху скрипта).
  3. Создайте функцию cleanup() с проверками и безопасными удалениями.
  4. Установите trap “cleanup; exit” для SIGINT SIGTERM SIGHUP и trap cleanup EXIT.
  5. Тестируйте сценарии с инструментами (kill, pkill, systemd‑stop, терминалы).
  6. Логируйте события очистки для аудита.

Мини‑шаблон cleanup:

cleanup() {
  # проверяем, задана ли переменная и существует ли файл
  if [[ -n "$temp_file" && -e "$temp_file" ]]; then
    rm -f "$temp_file" || echo "Не удалось удалить $temp_file"
  fi
  # остановить фоновые процессы по группе
  if [[ -n "$child_pgid" ]]; then
    kill -- -$child_pgid
  fi
}

trap 'cleanup; exit' SIGINT SIGTERM SIGHUP
trap cleanup EXIT

Риски и смягчения

Риск: handler выполняет долгую операцию и получает повторный сигнал.

  • Смягчение: в обработчике ставьте блокировку (файл‑блок, флаг) или переключайтесь в краткую ветку, которая только помечает факт и возвращает контроль основному циклу.

Риск: некорректное использование rm в обработчике.

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

Риск: несовместимость между системами.

  • Смягчение: документируйте минимальную целевую платформу (например, bash >= 4), используйте portable‑конструкции.

Советы по безопасности и соответствию

  • Не храните конфиденциальные данные в временных файлах без прав доступа: используйте umask и mktemp для создания безопасных временных файлов.
  • Если ваш скрипт запускается от имени сервиса (systemd), обрабатывайте сигналы корректно и используйте systemd‑юниты с KillMode=control‑group для завершения всех дочерних процессов.
  • Для обработки секретов предпочтительны in‑memory решения и закрытие дескрипторов в cleanup.

Совместимость и миграция

  • Linux (bash) — trap и SIG* работают ожидаемо.
  • BSD/FreeBSD — набор сигналов совпадает, но номера могут отличаться; всегда полагайтесь на имена.
  • Solaris/OpenIndiana — могут быть дополнительные сигналы; тестируйте в целевой среде.

Совет: при переносе скриптов между системами добавьте тест совместимости: trap -l и анализ вывода.

Ролевые чек‑листы

Для разработчика:

  • Идентифицированы все ресурсы, требующие очистки.
  • Добавлена функция cleanup и trap для SIGINT/SIGTERM/SIGHUP.
  • Код обработчиков краткий и идемпотентный.
  • Добавлены юнит‑ и интеграционные тесты на поведение при сигналах.

Для системного администратора:

  • Настроено логирование завершений/сбоев для анализа.
  • Тестирование поведения при обновлениях и рестартах сервиса.
  • Обеспечена совместимость с systemd/upstart/inetd как требуется.

Playbook внедрения в проект

  1. Проанализируйте текущие скрипты и выделите критические.
  2. Для каждого скрипта добавьте секцию переменных ресурсов вверху.
  3. Создайте cleanup() и поставьте trap на SIGINT SIGTERM SIGHUP и EXIT.
  4. Выполните ручное тестирование и сценарии отказа.
  5. Добавьте автоматические тесты, покрывающие поведение при сигналах.
  6. Обновите документацию и SOP для оперативного персонала.

Примеры расширённых сценариев

  1. Скрипт‑демон, создающий PID‑файл и запускающий дочерние процессы:
  • Создайте PID‑файл atomically (запись в tmp + mv) и укажите его в cleanup.
  • Смело используйте группы процессов: при старте инициируйте set -m и сохраняйте PGID, чтобы при завершении использовать kill – -$PGID.
  1. Скрипт, управляющий iptables/netfilter:
  • В cleanup снимайте правила, добавленные скриптом, по идентификатору или метке; не удаляйте все правила целиком.
  1. Скрипт, использующий TCP‑порт:
  • Закрывайте слушающий сокет в cleanup; учитывайте TIME_WAIT и повторные старты.

Решение задач: decision tree

flowchart TD
  A[Получен сигнал] --> B{Это SIGKILL или SIGSTOP?}
  B -- Да --> Z[Немедленное действие ядра, нет обработчика]
  B -- Нет --> C{Есть trap для сигнала?}
  C -- Да --> D[Вызвать обработчик]
  C -- Нет --> E{Это SIGTERM или SIGINT?}
  E -- Да --> F[Применяется поведение по умолчанию: завершение]
  E -- Нет --> G[Поведение зависит от сигнала 'stop/continue/core' ]
  D --> H{Обработчик завершает мгновенно?}
  H -- Да --> I[Выполнить exit -> EXIT handler -> выход]
  H -- Нет --> J[Установить флаг, вернуть управление основному циклу]
  J --> K[Основной цикл завершает неотложные задачи -> exit]

Глоссарий в одну строку

  • trap — встроенная bash‑команда для установки обработчиков сигналов.
  • SIGTERM — вежливый сигнал завершения, можно перехватить.
  • SIGKILL — мгновенное убийство процесса, нельзя перехватить.
  • EXIT — псевдо‑сигнал оболочки, вызывается при завершении скрипта.

Примеры тест‑кейсов и критерии приёмки

Тест‑кейсы:

  • При отправке SIGTERM скрипт удаляет все временные файлы, записанные им самими.
  • При трёх подряд SIGINT скрипт переходит в режим завершения и запускает exit‑handler.
  • При немедленном SIGKILL скрипт не успевает удалить временные файлы (ожидаемое поведение).

Критерии приёмки:

  • 100% чистка ресурсов при SIGTERM/SIGINT в тестовой среде.
  • Логи фиксируют запуск cleanup с временной меткой и перечислением очищенных ресурсов.
  • Отсутствие «зависших» портов или процессов после корректного завершения.

Типичные ошибки и как их избежать

Ошибка: выполнять длительные операции в обработчике.

  • Решение: только «малые» операции в обработчике, основная работа — в основном потоке по флагу.

Ошибка: удаление по пустой переменной: rm -rf “$temp_dir” (если переменная пуста, опасно).

  • Решение: проверять переменную: [[ -n “$temp_dir” ]] && rm -rf “$temp_dir”.

Ошибка: ожидание пользовательского ввода в обработчике.

  • Решение: никакого ввода — обработчик должен быть автономным.

Заключение

Ловля и обработка сигналов — простая, но критически важная часть написания надёжных Bash‑скриптов. Небольшие усилия — централизованный cleanup, аккуратные обработчики и тесты — значительно снижают риск накопления временных файлов, «подвисших» портов и других побочных эффектов. Начните с шаблона cleanup/trap, протестируйте его и интегрируйте в CI, чтобы гарантировать повторяемое и безопасное поведение.

Важно: всегда тестируйте на целевой платформе и документируйте поведение при сигналах в README скрипта.

Сводка

  • Добавляйте trap в начало скрипта.
  • Делайте обработчики короткими и идемпотентными.
  • Используйте EXIT для окончательной очистки.
  • Тестируйте все варианты (SIGTERM, SIGINT, SIGKILL — ожидание поведения).
Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Советы по уборке и организации дома — 5 ресурсов
Дом и быт

Советы по уборке и организации дома — 5 ресурсов

Rufus — AI-помощник покупок на Amazon
Обзоры

Rufus — AI-помощник покупок на Amazon

Как скрыть или показать пост в Facebook для конкретных людей
Социальные сети

Как скрыть или показать пост в Facebook для конкретных людей

Заблокировать профиль Chrome паролем
Безопасность

Заблокировать профиль Chrome паролем

Как начать вести влог — руководство для новичков
Влогинг

Как начать вести влог — руководство для новичков

Как зарабатывать Microsoft Rewards на Xbox
Гайды

Как зарабатывать Microsoft Rewards на Xbox