0Pricing
Linux Command Line & Bash Scripting Mastery · Урок

Статический анализ и аудит с ShellCheck

Встраивайте ShellCheck в этап проверки безопасности и интерпретируйте его результаты, укрепляя каждый скрипт

«Статический анализ и аудит с ShellCheck» — бесплатный урок Linux Command Line & Bash Scripting Mastery на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Linux Command Line & Bash Scripting Mastery, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Linux Command Line & Bash Scripting Mastery содержит 4 уроков всего.

Что такое ShellCheck и почему это важно

ShellCheck — это статический анализатор с открытым исходным кодом для скриптов оболочки. Он разбирает исходный код Bash (а также POSIX sh, dash и ksh), не выполняя его, и сообщает об ошибках, небезопасных конструкциях, проблемах переносимости и нарушениях стиля; каждому сообщению присваивается уникальный код правила, например SC2086.

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

  • Многие уязвимости оболочки (разбиение слов, внедрение, незакавыченные подстановки) незаметны при проверке штатных сценариев, но проявляются при вводе под контролем злоумышленника.
  • ShellCheck обнаруживает такие классы ошибок до выполнения и без дополнительных затрат.
  • Он объясняет, почему каждый шаблон опасен, благодаря чему со временем повышается осведомлённость команды.

Установите его в любой системе:

# Debian / Ubuntu
sudo apt-get install shellcheck

# macOS (Homebrew)
brew install shellcheck

# From source via Cabal (any platform)
cabal update && cabal install ShellCheck

# Verify
shellcheck --version

Первый запуск ShellCheck

Самый простой вызов — shellcheck <script>. ShellCheck читает строку shebang, чтобы определить диалект оболочки, а затем выводит результаты в stdout.

Каждое сообщение содержит:

  • имя файла и номер строки — точное расположение
  • уровень серьёзности — error, warning, info или style
  • код SC — стабильный идентификатор правила, который можно найти в документации или подавить
  • объяснение для человека — сообщает, что неверно и часто как это исправить

Запустите приведённый ниже скрипт и посмотрите на вывод, который сформировал бы ShellCheck:

#!/usr/bin/env bash
# demo_bad.sh — intentionally flawed for ShellCheck demonstration

FILE=$1

if [ $FILE == '' ]; then
  echo "No file given"
fi

cat $FILE | grep 'error' | wc -l

Интерпретация вывода ShellCheck и кодов SC

Для скрипта из предыдущего раздела ShellCheck сформировал бы примерно такие сообщения:

  • SC2086 (предупреждение) — Заключите в двойные кавычки, чтобы предотвратить подстановку шаблонов и разбиение слов — для $FILE в [ $FILE == '' ] и в cat $FILE.
  • SC2039 / SC3010 (информация) — == в [ ] является расширением Bash; для POSIX используйте =.
  • SC2002 (стиль) — Бесполезный cat. Вместо cat file | cmd рассмотрите cmd < file.

Каждому коду SC соответствует страница вики по адресу https://www.shellcheck.net/wiki/SCxxxx с обоснованием и исправленным примером.

Исправленная версия этого скрипта:

#!/usr/bin/env bash
# demo_fixed.sh — ShellCheck-clean

FILE="$1"

if [ -z "$FILE" ]; then
  echo "No file given" >&2
  exit 1
fi

grep -c 'error' "$FILE"

Уровни серьёзности и требуемые действия

ShellCheck классифицирует каждое сообщение по уровню серьёзности. В шлюзе безопасности следует действовать так:

  • error — почти наверняка ошибка или уязвимость. Заблокируйте сборку. Немедленно исправьте. Пример: SC2148 — отсутствует shebang, SC2070 — незакавыченный $?.
  • warning — шаблон высокого риска, который часто можно использовать для атаки. Заблокируйте сборку. Исправьте или явно обоснуйте подавление. Пример: SC2086 — незакавыченная переменная.
  • info — сегодня, вероятно, всё работает правильно, но конструкция хрупкая или непереносимая. Исправьте в том же PR, если проблема не признана выходящей за рамки.
  • style — косметическое замечание или рекомендация POSIX. Рекомендуется, но необязательно в проекте на чистом Bash.

Используйте --severity=warning, чтобы завершать работу с ненулевым кодом только при наличии предупреждений и сообщений более высокого уровня — это стандартный порог шлюза безопасности:

#!/usr/bin/env bash
# gate.sh — fail CI on errors and warnings only
shellcheck --severity=warning scripts/*.sh
echo "ShellCheck exit code: $?"

Интеграция ShellCheck в качестве шлюза безопасности CI

Шлюз безопасности полезен только тогда, когда он обязателен и автоматизирован. Приведённый ниже шаблон оборачивает ShellCheck в шаг CI, который:

  • Находит каждый файл .sh в репозитории.
  • Запускает ShellCheck с параметром --severity=warning и машиночитаемым выводом в формате JSON.
  • Завершает конвейер с ошибкой (exit 1), если найдено хотя бы одно сообщение.
  • Выводит сводку, чтобы разработчики могли обработать результаты, не покидая журнал CI.

Поместите этот файл в репозиторий и вызывайте его из конвейера CI (GitHub Actions, Jenkins, GitLab CI и т. д.):

#!/usr/bin/env bash
# ci/shellcheck_gate.sh
set -euo pipefail

SCRIPTS=$(find . -name '*.sh' -not -path './.git/*')
FAILED=0

for script in $SCRIPTS; do
  echo "==> Checking: $script"
  if ! shellcheck --severity=warning --format=tty "$script"; then
    FAILED=1
  fi
done

if [ "$FAILED" -eq 1 ]; then
  echo "[GATE] ShellCheck found warnings or errors. Build blocked." >&2
  exit 1
fi

echo "[GATE] All scripts passed ShellCheck."

Семейство SC2086: незакавыченные подстановки переменных

SC2086 — самое распространённое сообщение ShellCheck и одна из наиболее часто используемых уязвимостей оболочки: неза>кавыченные подстановки переменных.

Если переменная не заключена в двойные кавычки, оболочка выполняет для её значения разбиение слов (по IFS) и подстановку шаблонов. Злоумышленник, контролирующий переменную, может внедрить дополнительные аргументы, вызвать обход файловой системы или заставить команды получить неожиданные операнды.

Классический опасный шаблон:

#!/usr/bin/env bash
# Attacker sets: FILENAME="important.txt /etc/passwd"
FILENAME="$1"

# UNSAFE — word splitting turns this into two args
rm $FILENAME

# SAFE — double quotes prevent splitting
rm "$FILENAME"

# Arrays are the right tool for lists
FILES=("$@")
rm -- "${FILES[@]}"

Обнаружение риска внедрения команд с помощью SC2046 и SC2035

Два менее известных, но критически важных правила касаются внедрения команд через вывод подоболочки:

  • SC2046 — Заключите это в кавычки, чтобы предотвратить разбиение слов и подстановку шаблонов внутри $(…). Если вывод подоболочки используется без кавычек, любой пробел или символ шаблона в выводе превращается в токен оболочки.
  • SC2035 — Используйте ./*.sh вместо *.sh, чтобы имена файлов, начинающиеся с -, не интерпретировались как параметры (классический вектор внедрения аргументов).

Конкретный сценарий эксплуатации и исправление:

#!/usr/bin/env bash
# SC2046 example — output of find fed unquoted to chmod
# If a filename contains spaces, extra arguments appear

# UNSAFE
chmod 600 $(find /secrets -name '*.key')

# SAFE — use a while-read loop or xargs with -0
find /secrets -name '*.key' -print0 \
  | xargs -0 chmod 600

# SC2035 example
# UNSAFE — a file named '-rf' would be passed as an option
rm *.sh

# SAFE
rm -- ./*.sh

Использование формата вывода JSON для автоматизации

ShellCheck поддерживает несколько форматов вывода с помощью --format:

  • tty (по умолчанию) — вывод в терминале, удобный для чтения
  • json — машиночитаемый формат; идеален для панелей мониторинга, пользовательских блокировок или загрузки на платформы SAST
  • gcc — совместим с инструментами, разбирающими формат ошибок GCC (IDE, Vim/Emacs)
  • checkstyle — формат XML, который обрабатывает подключаемый модуль Checkstyle для Jenkins

Формат JSON позволяет создавать автоматические политики, например блокировать сборку только по определённым кодам SC или объединять результаты для большой кодовой базы в один отчёт о безопасности.

#!/usr/bin/env bash
# Emit JSON and filter for only error-severity findings using jq
shellcheck --format=json scripts/deploy.sh \
  | jq '[.[] | select(.level == "error")]'

# Count distinct SC codes across all scripts
find . -name '*.sh' -print0 \
  | xargs -0 shellcheck --format=json 2>/dev/null \
  | jq '[.[] | .code] | group_by(.) | map({code: .[0], count: length}) | sort_by(-.count)'

Корректное подавление ложных срабатываний

Безусловное отключение ShellCheck сводит на нет его предназначение. Правильный подход — это точечное подавление с документированным обоснованием, которое затрагивает только конкретную строку или блок, где сообщение действительно неприменимо.

Три механизма подавления:

  • Отключение в строке — # shellcheck disable=SC2086 в строке перед проблемным кодом. Затрагивает только эту строку.
  • Отключение и включение блока — окружите раздел директивами # shellcheck disable=… и # shellcheck enable=….
  • Директива на уровне файла — поместите # shellcheck disable=… в начало файла (оправдано редко; укажите причину).

Каждое подавление должно сопровождаться комментарием, объясняющим, почему сообщение является ложным срабатыванием:

#!/usr/bin/env bash
# deploy.sh

# Legitimate suppression: $DEPLOY_ARGS is intentionally word-split
# because it is a pre-validated list of flags from a trusted config file.
# shellcheck disable=SC2086
exec deploy-tool $DEPLOY_ARGS

# Block suppression for a section that generates dynamic code
# shellcheck disable=SC2016
VARS='$HOME $PATH $USER'
echo "Unexpanded vars: $VARS"
# shellcheck enable=SC2016

Настройка ShellCheck через .shellcheckrc

Для общих настроек проекта ShellCheck ищет .shellcheckrc, начиная с каталога скрипта и поднимаясь вверх до /. Это позволяет не повторять параметры при каждом вызове и сохраняет простоту скриптов шлюза.

Полезные директивы в .shellcheckrc:

  • shell=bash — переопределяет определение диалекта (полезно для файлов без shebang)
  • enable=all — включает необязательные проверки (например, avoid-nullary-conditions, require-variable-braces)
  • disable=SC2059 — подавление на уровне проекта для обоснованного исключения
  • external-sources=true — отслеживает и проверяет директивы source / .
# .shellcheckrc — project root
shell=bash
enable=all
external-sources=true

# SC2312: consider invoking this command separately to avoid masking its
# return value — suppressed project-wide because we use set -e.
# Rationale: errexit already aborts on failure; masking risk is mitigated.
disable=SC2312

Защищённый скрипт от начала до конца: до и после

Лучший способ усвоить сообщения ShellCheck — переработать реалистичный скрипт из состояния с ошибками в чистый и защищённый вариант. Приведённый ниже скрипт создаёт резервную копию каталога и был написан без учёта безопасности. Он нарушает как минимум пять различных правил ShellCheck.

Изучите обе версии. Вариант после проходит shellcheck --severity=warning без директив подавления и значительно безопаснее при вводе под контролем злоумышленника:

#!/usr/bin/env bash
# BEFORE — multiple ShellCheck violations
DEST=$1
SRC=$2
DATE=`date +%Y%m%d`

if [ ! -d $DEST ]; then
  mkdir $DEST
fi

cp -r $SRC $DEST/$DATE
echo Done


#!/usr/bin/env bash
# AFTER — ShellCheck-clean and hardened
set -euo pipefail

DEST="${1:?Usage: backup.sh <dest> <src>}"
SRC="${2:?Usage: backup.sh <dest> <src>}"
DATE=$(date +%Y%m%d)

if [ ! -d "$DEST" ]; then
  mkdir -p -- "$DEST"
fi

cp -r -- "$SRC" "$DEST/$DATE"
echo 'Done' >&2

Проверка знаний: ShellCheck как шлюз безопасности

Проверьте, насколько хорошо Вы понимаете роль ShellCheck как шлюза безопасности.

Итоги: статический анализ как шлюз безопасности

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

  • ShellCheck выполняет статический анализ без запуска скриптов, обнаруживая ошибки заключения в кавычки, риски внедрения и небезопасные шаблоны до выполнения.
  • Каждое сообщение содержит код SC (например, SC2086), связанный с подробной документацией и рекомендациями по исправлению.
  • Шкала серьёзности — error, warning, info, style — позволяет настроить шлюз: --severity=warning является рекомендуемым порогом безопасности.
  • Используйте машиночитаемый вывод (--format=json) для автоматизации отчётности, отслеживания тенденций и интеграции с SAST.
  • Подавляйте сообщения только в необходимых случаях: всегда ограничивайте подавление одной строкой, всегда документируйте причину в комментарии и никогда не отключайте проверки глобально без обоснования в .shellcheckrc.
  • Сочетайте ShellCheck с set -euo pipefail, явным заключением в кавычки, терминаторами -- argument и проверкой ввода для многоуровневой защиты.

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

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

Урок «Статический анализ и аудит с ShellCheck» бесплатный?

Да — полный текст урока «Статический анализ и аудит с ShellCheck» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Linux Command Line & Bash Scripting Mastery, подпишись на CoddyKit PRO. Курс Linux Command Line & Bash Scripting Mastery содержит 4 уроков всего.

Чему я научусь в уроке «Статический анализ и аудит с ShellCheck»?

Встраивайте ShellCheck в этап проверки безопасности и интерпретируйте его результаты, укрепляя каждый скрипт Ты практикуешь Linux Command Line & Bash Scripting Mastery с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Linux Command Line & Bash Scripting Mastery?

Предыдущий опыт не требуется. Linux Command Line & Bash Scripting Mastery на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «Статический анализ и аудит с ShellCheck»?

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

Можно ли писать и запускать код в этом уроке Linux Command Line & Bash Scripting Mastery?

Да. Каждый урок Linux Command Line & Bash Scripting Mastery включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

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

  1. Предотвращение внедрения команд и аргументов
  2. Безопасная работа с секретами и чистота окружения
  3. Выполнение с минимальными привилегиями и дисциплина sudo
  4. Статический анализ и аудит с ShellCheck
← Назад к Linux Command Line & Bash Scripting Mastery