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

Безопасное и надёжное программирование на Bash

9 min read Bash Обновлено 22 Oct 2025
Безопасное программирование на Bash
Безопасное программирование на Bash

Изображение ниже демонстрирует клавиатуру на Linux‑ПК с особенными символами, которые часто встречаются в скриптах и командах.

Клавиатура на Linux‑ПК, показывающая специальные символы.

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

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

Основные цели статьи

  • Объяснить, какие настройки и приёмы повышают надёжность Bash‑скриптов.
  • Показать примеры и типовые ошибки.
  • Дать готовые шаблоны, чеклисты и тесты для интеграции в рабочие процедуры.

Используйте корректную строку shebang

Первая строка скрипта должна явно указывать интерпретатор. Это делает скрипт автономным и документирует, на чём он работает. Есть два популярных подхода, и важно понимать разницу:

Пример с явным путём к bash:

#!/bin/bash
echo "Hello, world"

Этот вариант гарантированно запустит конкретный исполняемый файл по пути /bin/bash. Это удобно, когда вы хотите зафиксировать версию и поведение интерпретатора.

Пример с env — более переносимый:

#!/usr/bin/env bash
echo "Hello, world"

env в этом случае ищет bash в PATH и запускает его. Это удобно, если bash может находиться в разных местах на разных системах (например, /usr/bin, /usr/local/bin, ~/bin). Это повышает переносимость, но может снизить предсказуемость, если в PATH присутствует нежелательная версия bash.

Рекомендация: для скриптов, предназначенных для широкого распространения, используйте /usr/bin/env; для системных скриптов и сценариев с высокими требованиями безопасности можно зафиксировать путь.

Важно: строка shebang должна быть первой строкой файла (без BOM и пустых строк до неё).

Всегда заключайте переменные в кавычки

Пробелы и спецсимволы в путях — частая причина багов. При развёртывании переменных Bash делает это буквально, и если не взять значение в кавычки, слово будет разбито на аргументы.

Плохой пример:

#!/bin/bash

FILENAME="docs/Letter to bank.doc"
ls $FILENAME

После подстановки это эквивалентно:

ls docs/Letter to bank.doc

Bash увидит три аргумента: “docs/Letter”, “to” и “bank.doc”.

Правильно — всегда в кавычках:

ls "$FILENAME"

Использование фигурных скобок полезно, когда после имени переменной идёт литерал:

echo "_${FILENAME}_ is one of my favourite files"

Без ${} Bash попытался бы найти переменную FILENAME_.

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

Останавливайте скрипт при ошибке

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

set -e

По сути, это заставляет скрипт завершаться сразу, когда команда возвращает ненулевой код. Дополнительно используйте:

set -o pipefail

По умолчанию в пайплайне (например, cmd1 | cmd2) код возврата берётся по последней команде; pipefail гарантирует, что если любая команда в пайпе завершилась с ошибкой, весь пайп вернёт ошибку.

Пример, где это важно:

#!/bin/bash
set -e
set -o pipefail

touch /file
echo "Now do something with that file..."

Если touch не сработает (нет прав или несуществующая файловая система), скрипт завершится сразу, не переходя к последующим шагам.

Важно: set -e не заменяет обработку ошибок там, где необходимо специальное поведение — он служит защитной сеткой, а не единственной линией защиты.

Растите ответственность: обрабатывайте конкретные ошибки

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

Проверка через $?:

cd "$DIR"
if [ $? -ne 0 ]; then
  echo "Не удалось перейти в каталог $DIR" >&2
  exit 1
fi

Короткая запись через логический оператор:

cd "$DIR" || { echo "Не удалось перейти в $DIR" >&2; exit 1; }

Или, если вы хотите продолжить при неудаче, явно обрабатывайте последствия:

if ! cp "$src" "$dst"; then
  echo "Копирование не удалось, выполняю откат" >&2
  rm -f "$dst" || true
  exit 1
fi

Совет: вывод ошибок в stderr — хорошая практика для совместимости с логгером и пайпами.

Отлаживайте каждую команду с xtrace

Для детальной отладки включите xtrace, чтобы shell печатал команды перед выполнением:

set -o xtrace
# или коротко:
set -x

Это особенно полезно при сложной подстановке переменных и при поиске того, как именно команды формируются перед выполнением.

Терминал с включённым xtrace, показывающий команды date и ls.

Не держите xtrace включённым в продакшен‑скриптах, если в выводе могут появиться секреты (пароли, токены). Для временной отладки можно включать/выключать динамически:

set -x
# действия для отладки
set +x

Используйте полные наименования опций при вызове команд

Короткие однобуквенные опции удобны в интерактиве, но в скриптах читаемость критична.

Плохо:

rm -rf filename

Лучше:

rm --recursive --force filename

Длинные опции само‑документируют намерение кода и облегчают поддержку. Если команда поддерживает оба варианта, отдавайте предпочтение понятным ключам в скриптах.

Современная нотация для подстановки команд

Вместо устаревших обратных апострофов используйте $(…):

VAR=$(ls)
# плохой вариант:
VAR2=`ls`

$(…) поддерживает вложенность и легче читается.

Задавайте значения по умолчанию через синтаксис параметров

Чтобы задать значение по умолчанию без лишнего кода:

CMD=${PAGER:-more}

Если PAGER задан, он используется; иначе — more.

Можно вкладывать конструкции:

DIR=${1:-${HOME:-/home/default}}

Это структура: аргумент командной строки → переменная окружения → жёстко заданный дефолт.

Используйте двойной дефис для явного разделения опций и аргументов

Файлы, имена которых начинаются с «-», могут быть ошибочно интерпретированы как опции. Пример опасного сценария:

echo "nothing much" > -a-silly-filename

Список каталога покажет файл, но прямой вызов может сломаться:

Создание файла с именем, начинающимся на дефис, и подтверждение его существования в терминале.

Ошибка ls при попытке обработать имя файла, начинающееся с дефиса — сообщение 'unrecognized option'.

Защититься просто: используйте “–“, чтобы сказать «всё, что после — это аргументы, а не опции»:

rm -- *.md

Или экранируйте каждый файл:

rm "-a-silly-filename"

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

Локальные переменные в функциях

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

Плохой пример (неожиданное изменение глобальной переменной):

#!/bin/bash

function run {
  DIR=`pwd`
  echo "doing something..."
}

DIR="/usr/local/bin"
run
echo $DIR

Чтобы избежать побочных эффектов, объявляйте переменные как local:

function run {
  local DIR=$(pwd)
  echo "doing something..."
}

Используйте local всегда для переменных, которые нужны только внутри функции.

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

Шпаргалка опций set и распространённые комбинации

  • set -e: прерывать выполнение при ошибке
  • set -u: считать ошибкой использование необъявленной переменной (treat unset variables as an error)
  • set -o pipefail: считать ошибкой любую команду в пайплайне
  • set -x: трассировка команд (xtrace)

Комбинация (часто рекомендуемая для надёжных скриптов):

set -euo pipefail

Здесь set -u помогает обнаружить опечатки в именах переменных. Однако будьте осторожны: если вы ожидаете, что переменные могут отсутствовать, используйте конструкцию ${VAR:-} или проверки до использования.

Важно: set -e и set -u могут менять поведение выражений и условий; тестируйте скрипт в среде, соответствующей рабочей.

Мини‑методология разработки Bash‑скрипта

  1. Определите цель и входные данные (аргументы, переменные окружения, файлы).
  2. Напишите простую функцию main с обработкой опций и помощью (–help).
  3. Установите строгие флаги: set -euo pipefail (или мягче, если требуется).
  4. Везде ставьте кавычки вокруг переменных.
  5. Используйте local в функциях.
  6. Тщательно обрабатывайте внешние команды (проверка кодов возврата, явная обработка ошибок).
  7. Добавьте unit‑подобные тесты (см. раздел тестов).
  8. Документируйте интерфейс: параметры, ожидаемые пути, поведение при ошибках.

Роль‑ориентированные чеклисты

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

  • Есть shebang и комментарий о предназначении скрипта
  • Использован set -euo pipefail или обоснованно смягчённый набор
  • Все переменные в кавычках
  • Локальные переменные в функциях
  • Наличие –help и проверок аргументов

Для DevOps/администратора при деплое:

  • Скрипт запускается под нужным пользователем
  • Отсутствуют жёсткие путаницы с PATH (либо ясно зафиксирован интерпретатор)
  • Логи и stderr направлены в систему логирования
  • Нет записи секретов в вывод при включённом set -x

Шаблоны и сниппеты (cheat sheet)

Базовый шаблон надёжного скрипта:

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

# Краткое описание: что делает скрипт
# Usage: script.sh [options]

usage() {
  cat <

Пояснения:

  • IFS явно ограничен, чтобы избежать непредсказуемой разбивки по пробелам.
  • ${1:-} защищает от set -u.

Типовые тесты и критерии приёмки

Критерии приёмки для простого скрипта копирования:

  • Скрипт корректно обрабатывает несуществующий исходный файл (возвращает ненулевой код и не создаёт целевой файл).
  • Скрипт корректно обрабатывает файл с пробелами в имени.
  • Скрипт не удаляет файлы вне целевого каталога.
  • При нехватке прав скрипт сообщает и возвращает код ошибки.

Минимальные тест‑кейсы (ручные или автоматизированные):

  1. Пустой аргумент → скрипт возвращает код ошибки и печатает help.
  2. Копирование файла “a b.txt” → успешное копирование.
  3. Попытка записи в каталог без прав → корректная обработка ошибки.
  4. Наличие файла с именем “-rf” в каталоге → rm вызывается с – и не удаляет лишнего.

Матрица рисков и смягчения

  • Риск: Подмена интерпретатора через PATH (env запускает нежелательный bash).

    • Смягчение: Зафиксируйте путь к bash для критичных сценариев или контролируйте PATH.
  • Риск: Утечка секретов при отладке (set -x).

    • Смягчение: Отключать xtrace перед операциями с секретами; фильтровать вывод.
  • Риск: Неправильная обработка имён файлов с пробелами или спецсимволами.

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

    • Смягчение: set -o pipefail, явная проверка кодов возврата.

Когда предложенные приёмы не подходят (примеры отказа)

  • Встраиваемые маленькие однострочники в crontab, где ввод известен и вы контролируете окружение — избыточная строгость может усложнить поддержку.
  • Очень старые системы с урезанным /bin/sh, где нет поддержки современных расширений — некоторые конструкции (${var:-}) могут вести себя иначе.

Всегда тестируйте в целевой среде.

Альтернативные подходы

  • Использование POSIX‑совместимого sh вместо bash для максимальной переносимости. Ограничение: теряете некоторые удобные расширения bash.
  • Перевод сложной логики на Python/Perl/Ruby: если задача требует сложных структур данных, обработка JSON/YAML или модульности — язык общего назначения будет безопаснее и удобнее.
  • Контейнеризация скриптов (Docker): фиксирует окружение и версию интерпретатора, но добавляет уровень инфраструктуры.

Выбор зависит от требований к переносимости, скорости и поддержке.

Безопасность и секреты

  • Никогда не логируйте пароли/токены в открытом виде. Фильтруйте вывод при включённой трассировке.
  • Если скрипт работает с пользовательскими данными, валидируйте входы и избегайте выполнения скомпонованных команд через eval.
  • Для операций с привилегиями используйте sudo с минимально необходимыми правами и документируйте ожидания.

Краткая галерея краёв‑случаев (edge cases)

  • Пустые имена файлов или символы нулевого байта (NUL) — Bash не сможет работать с NUL внутри строк и файловых имён.
  • Ограничения файловой системы — некоторые FS не поддерживают определённые символы в именах.
  • Переполнение аргументов командной строки — длинные массивы аргументов могут превысить ограничение ОС.

1‑строчный глоссарий

  • shebang: первая строка файла вида #!/path — указывает интерпретатор.
  • set -euo pipefail: набор опций для строгой обработки ошибок.
  • xtrace (set -x): режим трассировки команд.
  • IFS: Internal Field Separator, влияет на разбиение строк.

Сводка

  • Всегда используйте корректную строку shebang и контролируйте переносимость/безопасность.
  • Берите переменные в двойные кавычки и применяйте local в функциях.
  • Включайте set -euo pipefail в большинстве сценариев и используйте set -x только для отладки.
  • Защитите команды от странных имён файлов через “–“. Тестируйте в реальном окружении и добавляйте простые тесты и критерии приёмки.

Важно: эти практики повышают надёжность и предсказуемость скриптов, но не заменяют тщательного анализа рисков и тестирования в вашей инфраструктуре.

Ключевые действия: проверьте shebang, кавычки вокруг переменных, local в функциях, строгие флаги и обработку ошибок — и у вас значительно уменьшится количество инцидентов, связанных с Bash‑скриптами.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

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

Microsoft Mood Board: быстрый мудборд для идей
Дизайн

Microsoft Mood Board: быстрый мудборд для идей

Замена слов в Microsoft Word: быстрый гид
Документы

Замена слов в Microsoft Word: быстрый гид

Изменить GPS на iPhone с iToolab AnyGo
Технологии

Изменить GPS на iPhone с iToolab AnyGo

Установка Elasticsearch на Ubuntu
DevOps

Установка Elasticsearch на Ubuntu

Не удаётся войти в аккаунт в Windows 11
Windows 11

Не удаётся войти в аккаунт в Windows 11

Как создать децентрализованный сайт — шаги и советы
Веб-разработка

Как создать децентрализованный сайт — шаги и советы