Обработчики trap для очистки и сигналов
Регистрируйте ловушки EXIT, ERR и INT, чтобы надёжно удалять временные файлы и откатывать незавершённые операции
«Обработчики trap для очистки и сигналов» — бесплатный урок DevOps Bootcamp на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения DevOps Bootcamp, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс DevOps Bootcamp содержит 4 уроков всего.
Зачем нужны обработчики ловушек
Когда сценарий Bash завершается — штатно, из-за ошибки или потому, что пользователь нажал Ctrl+C, — после него могут остаться временные файлы, частично записанные данные или нарушенное состояние. Без очистки эти остатки накапливаются и приводят к трудно диагностируемым проблемам.
Обработчики ловушек решают эту проблему: они регистрируют функцию или команду, которую Bash автоматически выполняет при получении определённого сигнала или псевдосигнала.
- EXIT — срабатывает при завершении сценария по любой причине
- ERR — срабатывает после того, как любая команда возвращает ненулевой статус завершения
- INT — срабатывает, когда пользователь отправляет SIGINT (Ctrl+C)
- TERM — срабатывает при SIGTERM (например, от
kill)
Хорошо написанный сценарий регистрирует эти ловушки в самом начале, до начала любых рискованных операций.
Синтаксис встроенной команды trap
Встроенная команда trap регистрирует обработчик для одного или нескольких сигналов или псевдосигналов. Общий синтаксис:
trap 'command_or_function' SIGNAL [SIGNAL...]
Основные правила:
- Первый аргумент — заключённая в кавычки строка кода оболочки (или имя функции), которая выполняется при срабатывании сигнала.
- После обработчика можно указать несколько сигналов.
trap '' SIGNALигнорирует этот сигнал (пустой обработчик).trap - SIGNALсбрасывает сигнал к поведению по умолчанию.
Ловушки наследуются функциями, вызываемыми в той же оболочке, но не наследуются вложенными оболочками, запущенными с помощью ( ) или &.
#!/usr/bin/env bash
# Syntax examples — not a full script
# Register a cleanup function on EXIT
trap cleanup EXIT
# Inline handler for INT and TERM
trap 'echo "Interrupted!"; exit 1' INT TERM
# Ignore SIGHUP
trap '' HUP
# Reset SIGPIPE to default
trap - PIPEЛовушка EXIT — Ваша страховочная сеть
Псевдосигнал EXIT — самая важная ловушка для регистрации. Она срабатывает при завершении оболочки по любой причине: штатном завершении, вызове exit N, необработанной ошибке или сигнале. Поэтому она идеально подходит для удаления временных файлов.
Рекомендуемый подход:
- Создавайте временные файлы с помощью
mktemp, чтобы их имена были уникальными и непредсказуемыми. - Сразу сохраняйте путь в переменной.
- Сразу после создания регистрируйте ловушку EXIT, чтобы очистка выполнялась всегда.
Код завершения сценария сохраняется — код завершения обработчика ловушки игнорируется, если только внутри него явно не вызвать exit.
#!/usr/bin/env bash
set -euo pipefail
TMPFILE=$(mktemp /tmp/myapp.XXXXXX)
cleanup() {
echo "Removing temp file: $TMPFILE" >&2
rm -f "$TMPFILE"
}
trap cleanup EXIT
# Do real work
echo "Processing..." > "$TMPFILE"
sort "$TMPFILE" -o "$TMPFILE"
cp "$TMPFILE" /tmp/myapp_result.txt
echo "Done. Result saved."Обработка нескольких временных файлов и каталогов
Реальные сценарии часто создают несколько временных объектов — файлы, каталоги, FIFO и файлы блокировок. Вместо того чтобы отслеживать каждый объект отдельно, соберите их в массив и удаляйте всё одной функцией очистки.
Этот подход легко масштабируется: при каждом создании нового временного ресурса добавляйте его в массив. Ловушка EXIT всегда обрабатывает весь список независимо от того, где завершается сценарий.
#!/usr/bin/env bash
set -euo pipefail
CLEANUP_TARGETS=()
add_cleanup() {
CLEANUP_TARGETS+=("$1")
}
cleanup() {
echo "Running cleanup..." >&2
for target in "${CLEANUP_TARGETS[@]:-}"; do
rm -rf "$target"
echo "Removed: $target" >&2
done
}
trap cleanup EXIT
# Create and track temp resources
TMPDIR_WORK=$(mktemp -d /tmp/work.XXXXXX)
add_cleanup "$TMPDIR_WORK"
TMPFILE_LOG=$(mktemp /tmp/run.XXXXXX.log)
add_cleanup "$TMPFILE_LOG"
echo "Working in $TMPDIR_WORK" | tee "$TMPFILE_LOG"
touch "$TMPDIR_WORK/output.txt"
echo "Step complete."Ловушка ERR — перехват сбоев
Псевдосигнал ERR срабатывает после того, как простая команда возвращает ненулевой статус завершения, если активна опция оболочки set -e (errexit) или команда не находится внутри составной конструкции if, while либо ||/&&.
Используйте ловушку ERR, чтобы:
- Записывать, какая строка завершилась с ошибкой, с помощью
$LINENOи$BASH_COMMAND. - Запускать откат до срабатывания ловушки EXIT.
- Выводить структурированные сообщения об ошибках в stderr.
Важно: ловушки ERR не срабатывают внутри функций, если явно не выполнить set -E (errtrace), чтобы функции унаследовали ловушку ERR от вызывающей оболочки.
#!/usr/bin/env bash
set -eEuo pipefail
on_error() {
local exit_code=$?
local line=$1
echo "ERROR: command failed with exit code $exit_code at line $line" >&2
echo " Command: $BASH_COMMAND" >&2
}
trap 'on_error $LINENO' ERR
echo "Starting task..."
cp /nonexistent/file /tmp/ # This will fail
echo "This line is never reached."Ловушка INT — корректная обработка Ctrl+C
Нажатие Ctrl+C отправляет SIGINT передней группе процессов. Без ловушки Bash немедленно завершает работу, и после него могут остаться незавершённые результаты.
Регистрация ловушки INT позволяет:
- Вывести понятное пользователю сообщение об отмене.
- Выполнить промежуточный откат перед передачей управления ловушке EXIT.
- Завершить работу со значимым кодом статуса (
130— общепринятый код для SIGINT:128 + 2).
После завершения обработчика INT вызовите exit 130, чтобы передать правильный код завершения. После этого ловушка EXIT сработает автоматически.
#!/usr/bin/env bash
set -euo pipefail
TMPFILE=$(mktemp /tmp/demo.XXXXXX)
cleanup() {
rm -f "$TMPFILE"
echo "Temp file removed." >&2
}
on_interrupt() {
echo "" >&2
echo "Caught SIGINT — cancelling gracefully." >&2
exit 130 # EXIT trap (cleanup) runs automatically after this
}
trap cleanup EXIT
trap on_interrupt INT
echo "Running long task. Press Ctrl+C to cancel."
for i in $(seq 1 10); do
echo "Step $i/10..."
sleep 1
done
echo "All steps complete."Объединение ловушек EXIT, ERR и INT
В рабочих сценариях обычно регистрируют все три ловушки вместе, чтобы охватить все пути завершения:
- EXIT — срабатывает всегда и выполняет очистку файлов.
- ERR — записывает команду, завершившуюся с ошибкой, и номер строки.
- INT — выводит сообщение об отмене и завершает работу с кодом 130.
Поскольку EXIT всегда срабатывает последней, логика очистки должна находиться только в одном месте. Обработчики ERR и INT могут сосредоточиться на записи событий и изменении состояния, а затем передать фактическую очистку ловушке EXIT, вызвав exit.
Используйте set -eEuo pipefail в начале, чтобы ошибки в функциях также вызывали ERR, а pipefail перехватывал сбои внутри конвейеров.
#!/usr/bin/env bash
set -eEuo pipefail
TMPFILE=$(mktemp /tmp/combined.XXXXXX)
ROLLBACK_NEEDED=false
cleanup() {
if [[ "$ROLLBACK_NEEDED" == true ]]; then
echo "Rolling back partial changes..." >&2
fi
rm -f "$TMPFILE"
echo "Cleanup done." >&2
}
on_error() {
ROLLBACK_NEEDED=true
echo "ERR at line $1: $BASH_COMMAND" >&2
}
on_interrupt() {
echo "Interrupted by user." >&2
exit 130
}
trap cleanup EXIT
trap 'on_error $LINENO' ERR
trap on_interrupt INT
echo "hello" > "$TMPFILE"
echo "Script finished successfully."Динамический сброс и отключение ловушек
Иногда требуется изменить ловушку в середине сценария — например, выполнить одноразовую очистку после завершения критической секции, чтобы последующие штатные операции не запускали ненужный откат.
trap - SIGNALсбрасывает сигнал к встроенному поведению по умолчанию.trap '' SIGNALполностью игнорирует сигнал (процесс нельзя завершить этим сигналом).- Новую обработку можно зарегистрировать в любой момент; действует последний вызов
trap.
Распространённый подход — изменить ловушку EXIT после успешного выполнения рискованной операции, заменив обработчик отката простой очисткой.
#!/usr/bin/env bash
set -euo pipefail
TMPFILE=$(mktemp /tmp/staged.XXXXXX)
rollback() {
echo "ROLLBACK: removing $TMPFILE" >&2
rm -f "$TMPFILE"
}
cleanup_only() {
echo "CLEANUP: removing $TMPFILE" >&2
rm -f "$TMPFILE"
}
# Start with rollback in case something fails during the risky phase
trap rollback EXIT
echo "Performing risky operation..."
echo "critical data" > "$TMPFILE"
# ... imagine more risky steps here ...
# Risky phase succeeded — switch to simple cleanup
trap cleanup_only EXIT
echo "Risky phase done. Now doing safe finalization."
cp "$TMPFILE" /tmp/staged_result.txt
echo "All done."Передача кодов завершения через ловушки
Есть одна неочевидная проблема: если функция очистки содержит команды, которые могут завершиться с ошибкой, итоговый код завершения сценария может быть перезаписан. Явно сохраняйте исходный код завершения.
В обработчике ловушки $? содержит код завершения, который вызвал срабатывание ловушки. Сохраните его в самой первой строке обработчика, до выполнения любой другой команды, которая может его изменить.
#!/usr/bin/env bash
set -euo pipefail
TMPFILE=$(mktemp /tmp/exitcode.XXXXXX)
cleanup() {
local original_exit=$? # capture BEFORE any other command
echo "Cleaning up (original exit: $original_exit)" >&2
rm -f "$TMPFILE"
# Exit with the original code so callers see the real result
exit "$original_exit"
}
trap cleanup EXIT
echo "Doing work..."
echo "data" > "$TMPFILE"
# Simulate failure
if [[ ! -f /tmp/required_marker ]]; then
echo "Required marker missing!" >&2
exit 2
fi
echo "Done."Ловушки в функциях и вложенных оболочках
Понимание наследования ловушек крайне важно для написания корректных сценариев:
- Функции выполняются в той же оболочке и наследуют ловушки, но ERR распространяется на функции только при активной опции
set -E(errtrace). - Вложенные оболочки (
( ... )) начинают работу с копией ловушек родительской оболочки, но изменения внутри вложенной оболочки не влияют на родительскую. - Фоновые задания (
cmd &) являются вложенными оболочками и не наследуют ловушки INT/TERM родительской оболочки (для них действует стандартная обработка сигналов).
Практическое правило: регистрируйте ловушки в основном теле сценария, используйте set -E, когда нужно распространить ERR на функции, и никогда не полагайтесь на наследование ловушек через &.
#!/usr/bin/env bash
set -eEuo pipefail # -E = errtrace: ERR propagates into functions
on_error() {
echo "ERR in function or main at line $1" >&2
}
trap 'on_error $LINENO' ERR
risky_function() {
echo "Inside risky_function"
ls /nonexistent_path # triggers ERR — works because of set -E
}
# Subshell: has its own copy; changes don't affect parent
(
trap '' ERR # disable ERR only inside subshell
ls /nonexistent_path 2>/dev/null || true
echo "Subshell completed without triggering parent ERR"
)
risky_functionПрактический шаблон: атомарная замена файла
Классический защищённый шаблон для Bash — атомарная замена файла: запись во временный файл с последующим переименованием на место исходного. Если до переименования произойдёт сбой, исходный файл останется неизменным. Ловушки делают этот подход надёжным.
Шаги:
- Создайте временный файл в той же файловой системе, что и файл назначения (чтобы
mvвыполняла атомарную операцию). - Зарегистрируйте ловушку EXIT для удаления временного файла при досрочном завершении.
- Запишите и проверьте новое содержимое.
- Атомарно переименуйте файл (
mv) — только теперь исходный файл будет заменён. - Ловушка EXIT удалит временный файл, только если он всё ещё существует (после успешного
mvон исчезает).
#!/usr/bin/env bash
set -euo pipefail
DEST="/tmp/important_config.conf"
TMPFILE=$(mktemp "$(dirname "$DEST")/.tmp.XXXXXX")
cleanup() {
rm -f "$TMPFILE" # no-op if mv already moved it
}
trap cleanup EXIT
# Write new content to temp file
cat > "$TMPFILE" <<'EOF'
[settings]
version=2
mode=production
EOF
# Validate before replacing
if ! grep -q 'version=' "$TMPFILE"; then
echo "Validation failed — aborting replacement" >&2
exit 1
fi
# Atomic rename — DEST is replaced only here
mv "$TMPFILE" "$DEST"
echo "Config updated atomically: $DEST"Проверка знаний: наследование ловушки ERR
Проверьте, как ведёт себя ловушка ERR при работе с функциями в Bash.
Итоги: обработчики ловушек для очистки и сигналов
В этом уроке Вы узнали, как сделать сценарии Bash устойчивыми с помощью обработчиков ловушек:
- EXIT — универсальная страховочная сеть: регистрируйте её сразу после создания любого временного ресурса и всегда сначала сохраняйте
$?, чтобы сохранить исходный код завершения. - ERR позволяет записывать сведения о сбоях с помощью
$LINENOи$BASH_COMMAND; используйтеset -E, чтобы функции наследовали эту ловушку. - INT корректно обрабатывает Ctrl+C: выведите сообщение и завершите работу с кодом 130, чтобы EXIT выполнила очистку.
- Собирайте временные ресурсы в массив и удаляйте их все одной функцией очистки.
- Используйте
trap - SIGNALдля сброса иtrap '' SIGNALдля динамического игнорирования сигналов. - Шаблон атомарного переименования (
mktemp→ запись → проверка →mv) в сочетании с ловушкой EXIT гарантирует, что частичные записи никогда не повредят файл назначения.
Сочетание set -eEuo pipefail с правильно размещёнными ловушками создаёт надёжную основу для защищённого от сбоев рабочего сценария Bash.
Часто задаваемые вопросы
Урок «Обработчики trap для очистки и сигналов» бесплатный?
Да — полный текст урока «Обработчики trap для очистки и сигналов» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс DevOps Bootcamp, подпишись на CoddyKit PRO. Курс DevOps Bootcamp содержит 4 уроков всего.
Чему я научусь в уроке «Обработчики trap для очистки и сигналов»?
Регистрируйте ловушки EXIT, ERR и INT, чтобы надёжно удалять временные файлы и откатывать незавершённые операции Ты практикуешь DevOps Bootcamp с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать DevOps Bootcamp?
Предыдущий опыт не требуется. DevOps Bootcamp на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Обработчики trap для очистки и сигналов»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке DevOps Bootcamp?
Да. Каждый урок DevOps Bootcamp включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Строгий режим с set -euo pipefail
- Обработчики trap для очистки и сигналов
- Безопасные временные файлы и каталоги блокировок
- Идемпотентные скрипты и логика повторных попыток с задержкой