0Pricing
DevOps Bootcamp · Урок

Строгий режим с set -euo pipefail

Включайте немедленное завершение при сбое и точно разбирайтесь, какие ошибки обнаруживает каждый флаг строгого режима, а какие пропускает

«Строгий режим с set -euo pipefail» — бесплатный урок DevOps Bootcamp на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения DevOps Bootcamp, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс DevOps Bootcamp содержит 4 уроков всего.

Почему Bash по умолчанию молча игнорирует ошибки

По умолчанию Bash продолжает работу, даже если команды завершаются с ошибкой. Это приводит к малозаметным и трудным для отладки сбоям в рабочих сценариях.

Рассмотрим сценарий, который пытается создать резервную копию:

  • Опечатка в пути приводит к сбою cp
  • Bash игнорирует сбой и продолжает работу
  • Сценарий сообщает об успехе, хотя данные так и не были сохранены в резервной копии

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

#!/usr/bin/env bash
# Without strict mode — dangerous default behavior

cp /important/data /backups/data   # fails (path doesn't exist)
echo "Backup complete"              # still prints — false confidence!
rm -rf /tmp/staging                # still runs — potentially destructive

Три основных флага: set -euo pipefail

Строгий режим включается добавлением этой строки в начале каждого сценария:

set -euo pipefail

Она активирует три независимых механизма защиты:

  • -e — немедленно завершать работу, если любая команда возвращает ненулевой статус
  • -u — считать неустановленные переменные ошибкой (вместо подстановки пустой строки)
  • -o pipefail — считать конвейер завершившимся с ошибкой, если сбой произошёл в любой его команде, а не только в последней

Вместе они образуют стандартный защитный заголовок серьёзных сценариев Bash. Каждый флаг выявляет свой тип ошибки.

#!/usr/bin/env bash
set -euo pipefail

echo "Strict mode is now active"
echo "Every command failure will abort the script"

Разбор set -e (errexit)

set -e (также записывается как set -o errexit) немедленно завершает сценарий, когда команда завершается с ненулевым статусом.

Основные особенности:

  • Простые команды: false, grep pattern file (совпадений нет), ls /nonexistent — все они вызывают завершение
  • Код завершения последней команды сценария становится кодом завершения самого сценария
  • Команды в условиях if не учитываются — -e не срабатывает для проверочного выражения
  • Команды, после которых стоит || true, также не учитываются (см. следующие разделы)

Воспринимайте -e как первую линию защиты от незаметного продолжения работы после сбоя.

#!/usr/bin/env bash
set -e

echo "Before failure"
ls /this/path/does/not/exist   # exits here with code 2
echo "This line never runs"

Разбор set -u (nounset)

set -u (также записывается как set -o nounset) заставляет Bash считать любое обращение к неустановленной переменной критической ошибкой.

Без -u опечатка вроде $FLENAME вместо $FILENAME молча заменяется пустой строкой, из-за чего команды начинают работать неожиданно — или опасно (представьте rm -rf "$TMPDIR/", если $TMPDIR не установлена).

Важные исключения:

  • ${VAR:-default} — безопасная подстановка значения по умолчанию, которая не вызывает -u
  • ${VAR:+value} — условная подстановка, также безопасная
  • "$@" и "$*" не учитываются, если позиционные аргументы не переданы
#!/usr/bin/env bash
set -euo pipefail

# Safe: provide a default for optional vars
OUTPUT_DIR="${1:-/tmp/output}"
LOG_LEVEL="${LOG_LEVEL:-info}"

echo "Writing to: $OUTPUT_DIR"
echo "Log level: $LOG_LEVEL"

# This would abort the script:
# echo "$UNDEFINED_VAR"   # bash: UNDEFINED_VAR: unbound variable

Разбор -o pipefail

Без pipefail статус завершения конвейера определяется исключительно последней командой. Сбои предыдущих команд молча игнорируются.

Пример без pipefail:

  • cat /missing/file | wc -l
  • cat завершается с кодом 1, но wc -l успешно завершается с кодом 0
  • Конвейер возвращает 0 — успех! Хотя данные были потеряны.

Если pipefail включён, Bash возвращает код завершения самой правой команды, завершившейся с ошибкой. Благодаря этому сбои в конвейере становятся видимыми и обрабатываемыми.

Примечание: pipefail не является буквенным флагом — его необходимо задавать с помощью -o pipefail.

#!/usr/bin/env bash
set -euo pipefail

# With pipefail: this aborts if grep finds nothing (exit 1)
# grep returns 1 when no match found
ps aux | grep "[n]ginx" | awk '{print $2}'

echo "If we reach here, nginx is running"

Преднамеренные сбои команд — использование || true

Иногда команде разрешено завершиться с ошибкой. При использовании set -e необходимо явно указать, что сбой допустим, иначе сценарий прервётся.

Идиоматическое решение — || true, добавляющее запасной вариант, который всегда завершается успешно:

  • command || true — полностью игнорировать сбой
  • command || echo "Warning: step failed, continuing" — записать предупреждение и продолжить работу
  • command || { echo "fatal"; exit 1; } — использовать собственную обработку сбоя

Этот шаблон явно выражает намерение в коде: обычная команда означает «эта команда обязана завершиться успешно», а || true — «эта команда может завершиться с ошибкой, и это допустимо».

#!/usr/bin/env bash
set -euo pipefail

# Remove temp dir if it exists — OK if it doesn't
rm -rf /tmp/my_workspace || true
mkdir -p /tmp/my_workspace

# Check if a service is running — OK if not
if systemctl is-active --quiet nginx 2>/dev/null || true; then
  echo "nginx is active"
fi

# Grep that may find nothing — OK
grep 'ERROR' /var/log/app.log || true

echo "Done"

Что set -e НЕ перехватывает

У set -e есть хорошо известные исключения и особенности. Понимание их помогает избежать ложной уверенности:

  • Команды в условиях if / while / until — проверочное выражение намеренно не учитывается
  • Команды, перед которыми стоит отрицание ! — ! false не вызывает завершение
  • Последняя команда перед || — например, false || handle_error
  • Статус завершения подоболочки в определённых контекстах — например, VAR=$(failing_command) в некоторых версиях Bash
  • Возвращаемые значения функций — учитывается только последняя команда функции

Строгий режим не заменяет явную проверку ошибок — это страховочная сетка, которая выявляет большинство случайных сбоев.

#!/usr/bin/env bash
set -euo pipefail

# These do NOT trigger -e:
if false; then echo "never"; fi         # -e exempt in conditions
! false                                  # negation exempts
false || echo "handled"                 # || exempts the left side

# This DOES trigger -e (no condition, no ||):
# false

echo "Script continues after exempted failures"

Подоболочки и функции в строгом режиме

Настройки строгого режима наследуются подоболочками, но в функциях и подстановках команд есть свои особенности.

Основные правила:

  • Функции наследуют -e, -u и pipefail от вызывающей оболочки
  • Ненулевое возвращаемое значение функции приводит к завершению вызывающей оболочки (если установлен -e) — за исключением случаев, когда вызов находится в условии или после ||
  • Подстановка команды $(): в старых версиях Bash сбой команды внутри $() может не вызвать -e в родительской оболочке; для безопасности сначала присвойте результат, а затем используйте его отдельной командой
  • Явные подоболочки () наследуют все флаги
#!/usr/bin/env bash
set -euo pipefail

setup_workspace() {
  local dir="$1"
  mkdir -p "$dir"          # fails here if permissions denied
  cd "$dir"
  echo "Ready in $(pwd)"
}

# Safe pattern: assign result then use it
TODAY=$(date +%Y-%m-%d)   # capture separately
WORKDIR="/tmp/run_${TODAY}"

setup_workspace "$WORKDIR"
echo "Workspace: $WORKDIR"

Сочетание строгого режима с перехватом ошибок

Строгий режим указывает Bash, когда остановиться. Обработчик trap для ERR позволяет выполнить очистку или диагностику до завершения сценария.

Распространённый шаблон:

  • Включить строгий режим в начале сценария
  • Определить функцию cleanup или on_error
  • Зарегистрировать её с помощью trap 'on_error' ERR
  • При необходимости также установить обработчик для EXIT, чтобы очистка гарантированно выполнялась независимо от успеха или сбоя

Важно: используйте set -E (заглавная E, также называется errtrace), чтобы обработчик ERR наследовался функциями и подоболочками. Без этого обработчики срабатывают только в основном теле оболочки.

#!/usr/bin/env bash
set -Eeuo pipefail

on_error() {
  local exit_code=$?
  local line_number=${BASH_LINENO[0]}
  echo "ERROR: command failed with code ${exit_code} at line ${line_number}" >&2
}

cleanup() {
  echo "Cleaning up temporary files..." >&2
  rm -rf /tmp/my_run_dir 2>/dev/null || true
}

trap on_error ERR
trap cleanup EXIT

mkdir -p /tmp/my_run_dir
echo "hello" > /tmp/my_run_dir/output.txt
cat /tmp/my_run_dir/output.txt
echo "Done"

Локальное отключение строгого режима

Иногда блок кода намеренно бывает «нестрогим» — например, при проверке наличия необязательных инструментов или запуске устаревших команд, которые возвращают ненулевой статус не из-за ошибки. Строгий режим можно временно отключить, а затем восстановить.

Безопасный шаблон:

  • Сохраните состояние с помощью set +e (отключает -e), выполните блок, а затем снова включите режим с помощью set -e
  • Или используйте подоболочку ( set +e; ... ), чтобы флаги родительской оболочки не изменялись
  • Всегда включайте флаги снова сразу после завершения рискованного блока — оставленные отключёнными флаги часто становятся источником ошибок

Если блок содержит несколько команд, предпочитайте вариант с подоболочкой: при завершении она автоматически восстанавливает флаги.

#!/usr/bin/env bash
set -euo pipefail

# Probe for optional tools without aborting
HAS_JQ=false
(
  set +e
  command -v jq > /dev/null 2>&1
  [[ $? -eq 0 ]] && echo "jq_found"
) && HAS_JQ=true || true

if [[ "$HAS_JQ" == "true" ]]; then
  echo "jq is available — using JSON output"
else
  echo "jq not found — using plain text"
fi

Полный шаблон сценария со строгим режимом

Ниже приведён готовый для рабочей среды шаблон, объединяющий все рассмотренные в этом уроке рекомендации по строгому режиму:

  • set -Eeuo pipefail — все четыре флага, включая errtrace
  • IFS=$'\n\t' — более безопасное разделение слов (не разделяет по пробелам)
  • Обработчики ERR и EXIT для диагностики и очистки
  • Явные значения по умолчанию для необязательных параметров
  • readonly и local для ограничения области видимости переменных

Копируйте этот шаблон в начало каждого нетривиального сценария Bash, чтобы сразу воспользоваться остановкой при первом сбое и возможностью отследить ошибки.

#!/usr/bin/env bash
set -Eeuo pipefail
IFS=$'\n\t'

# ── Constants ────────────────────────────────────────────
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "$0")"

# ── Trap handlers ────────────────────────────────────────
err_handler() {
  echo "[${SCRIPT_NAME}] ERROR on line ${BASH_LINENO[0]}: exit ${?}" >&2
}
cleanup() {
  echo "[${SCRIPT_NAME}] Exiting" >&2
}
trap err_handler ERR
trap cleanup EXIT

# ── Defaults ─────────────────────────────────────────────
ENV="${1:-production}"
MAX_RETRIES="${MAX_RETRIES:-3}"

# ── Main ─────────────────────────────────────────────────
main() {
  echo "Running in env=${ENV}, max_retries=${MAX_RETRIES}"
  echo "Script dir: ${SCRIPT_DIR}"
}

main "$@"

Проверка знаний: поведение pipefail

Проверьте, как pipefail влияет на коды завершения конвейера.

Итоги: строгий режим с set -euo pipefail

В этом уроке Вы узнали, как с помощью строгого режима сделать так, чтобы сценарии Bash быстро завершались при сбоях и явно сообщали об ошибках.

Три флага и то, от чего они защищают:

  • -e (errexit) — завершает выполнение при любом ненулевом статусе команды; не действует в условиях и после ||
  • -u (nounset) — прерывает выполнение при обращении к неустановленной переменной; для необязательных переменных используйте ${VAR:-default}
  • -o pipefail — считает весь конвейер завершившимся с ошибкой, если сбой произошёл на любом этапе, а не только на последнем

Дополнительные рекомендации:

  • Добавляйте -E (errtrace), чтобы ловушки ERR распространялись на функции
  • Используйте trap с ERR и EXIT для диагностики и очистки
  • Используйте || true, чтобы намеренно допускать сбои
  • Временно отключайте строгую обработку с помощью set +e во вложенных оболочках для устаревшего кода или проверочных операций

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

Часто задаваемые вопросы

Урок «Строгий режим с set -euo pipefail» бесплатный?

Да — полный текст урока «Строгий режим с set -euo pipefail» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс DevOps Bootcamp, подпишись на CoddyKit PRO. Курс DevOps Bootcamp содержит 4 уроков всего.

Чему я научусь в уроке «Строгий режим с set -euo pipefail»?

Включайте немедленное завершение при сбое и точно разбирайтесь, какие ошибки обнаруживает каждый флаг строгого режима, а какие пропускает Ты практикуешь DevOps Bootcamp с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать DevOps Bootcamp?

Предыдущий опыт не требуется. DevOps Bootcamp на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.

Сколько времени занимает урок «Строгий режим с set -euo pipefail»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке DevOps Bootcamp?

Да. Каждый урок DevOps Bootcamp включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Строгий режим с set -euo pipefail
  2. Обработчики trap для очистки и сигналов
  3. Безопасные временные файлы и каталоги блокировок
  4. Идемпотентные скрипты и логика повторных попыток с задержкой
← Назад к DevOps Bootcamp