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

Безопасное и надёжное программирование на 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
Автор
Редакция

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

Как устроить идеальную вечеринку для просмотра ТВ
Развлечения

Как устроить идеальную вечеринку для просмотра ТВ

Как распаковать несколько RAR‑файлов сразу
Инструменты

Как распаковать несколько RAR‑файлов сразу

Приватный просмотр в Linux: как и зачем
Приватность

Приватный просмотр в Linux: как и зачем

Windows 11 не видит iPod — способы исправить
Руководство

Windows 11 не видит iPod — способы исправить

PS5: как настроить игровые пресеты
Консоли

PS5: как настроить игровые пресеты

Как переключить камеру в Omegle на iPhone и Android
Руководство

Как переключить камеру в Omegle на iPhone и Android