$ sudo teach|
    $ sudo teach|
    IT school
  • Telegram
  • Партнёрам
  • Все курсы
$ sudo teach IT
OOO "SALEPROFIT"Контакты и реквизитыIT-Park Logo

Школа

  • Блог
  • Проверить сертификат

Сотрудничество

  • Стать учителем
  • Партнёрская программа
  • О проекте

Право

  • Оферта
  • Политика конфиденциальности

© 2023–2026 $ sudo teach IT™. All Rights Reserved. Public user contributions licensed under CC BY-SA 4.0 license with attribution required
TelegramGitHubYouTube
ГлавнаяБлогИзучаем bash “по месту”: работа с аргументами, безопасными переменными и функциями

Изучаем bash “по месту”: работа с аргументами, безопасными переменными и функциями

$ sudo teach IT
·29 июля 2026 г.·10 мин·83
Изучаем bash “по месту”: работа с аргументами, безопасными переменными и функциями

Разберём практики, которые чаще всего ломают bash-скрипты: парсинг аргументов, quoting, set -euo pipefail, функции и обработка ошибок. В конце — шаблон-скелет скрипта для повторного использования.

Содержание
Почему bash-скрипты ломаются: реальные причины1) Аргументы и пробелы: “оно же строка”2) Парсинг аргументов: “пробовали, но как-то работает”3) set -e без понимания семантики4) Ошибки в функциях “не те, где вы думаете”5) Пайплайны и скрытые ошибкиБазовый набор “безопасности” для bash: set -euo pipefail и дисциплинаАргументы: как парсить правильно и предсказуемо1) Минимальный принцип: “каждый флаг — это контракт”2) Используем getopts для коротких опций3) Длинные опции --long: разумная практика через case + переборБезопасные переменные и quoting: что именно делать1) Quoting — по умолчанию2) Когда нужен “неочевидный” default: ${var:-default} vs ${var:?message}3) read и массивы: не ломаем пробелыФункции: как писать их “как в коде”, а не “как в заметках”1) Функции должны быть маленькими и с явными входами/выходами2) Ошибки: return вместо exit внутри функций3) trap для централизованной обработкиОбработка ошибок без паники: практики, которые работают1) Там, где ошибка — это ожидаемое условие, обрабатывайте явно2) Используйте &&/|| только осознанно3) Валидация входных данных до выполнения опасных операцийТипичные подводные камни (короткий чеклист)Шаблон-скелет bash-скрипта: минимально надежный каркас для повторного использования

Bash часто воспринимают как “скрипты для разовых задач”. На практике же многие команды быстро превращаются в маленькие программы: появляются десятки аргументов, условия, обработка ошибок, логирование, повторяемость. И именно в этот момент “обычный bash” начинает ломаться — не из‑за отсутствия знаний, а из‑за типичных технологических ошибок: неудачный парсинг аргументов, отсутствие quoting, слепая опора на set -e, некорректная работа с переменными и ошибками в функциях.

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


Почему bash-скрипты ломаются: реальные причины

Разберем несколько распространенных сценариев.

1) Аргументы и пробелы: “оно же строка”

Самая частая причина неожиданных падений — забытый quoting.

Пусть есть команда:

code
cp $source $dest

Если $source содержит пробелы ("my file.txt"), bash разобьёт строку на слова, подставив параметры неверно. Правильный минимум:

code
cp "$source" "$dest"

Правило в bash простое: везде, где возможны пробелы, подстановка, пустые значения, glob’ы — используйте кавычки. Исключения есть, но они требуют осознанности.


2) Парсинг аргументов: “пробовали, но как-то работает”

Часто встречается самописный парсер на цикле shift и “костылях” вида:

code
while [ $# -gt 0 ]; do
  case $1 in
    --mode=*) mode="${1#*=}" ;;
    --mode) mode="$2"; shift ;;
    *) echo "unknown"; exit 1 ;;
  esac
  shift
done

Это работает до тех пор, пока:

  • не появляются аргументы без значения,
  • значения начинаются с -,
  • нужно поддержать повторяющиеся флаги,
  • формат усложняется (например, --path и --path=... одновременно).

В bash есть инструменты, которые уменьшают “случайность” (в частности, getopts, а для длинных опций — обычно приходится писать небольшую обвязку, или использовать getopt системный).


3) set -e без понимания семантики

set -e (“exit on error”) — не “магическая гарантия”. В bash поведение зависит от контекста: команда в if, while, в составе &&/||, в некоторых конструкциях не обязательно вызывает немедленный выход.

Добавьте к этому unset var, и вот вы уже ловите либо “скрипт внезапно падает”, либо “ошибка тихо проходит”.

На практике чаще всего рекомендуют комбинацию:

code
set -euo pipefail

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


4) Ошибки в функциях “не те, где вы думаете”

Частая проблема — функции возвращают ошибки, но их результат игнорируется. Или наоборот: функция делает что-то “опасное” и полагается на глобальные переменные.

В bash функции могут возвращать код через return (0..255). Если функция хочет “остановить весь скрипт”, это лучше сделать явно: например, через return 1 и обработку в вызывающем месте.


5) Пайплайны и скрытые ошибки

cmd1 | cmd2 в bash по умолчанию возвращает код последней команды (cmd2), а не cmd1. Поэтому важна опция pipefail:

  • set -o pipefail заставляет bash возвращать ненулевой статус, если хотя бы одна команда в пайплайне завершилась с ошибкой.

Базовый набор “безопасности” для bash: set -euo pipefail и дисциплина

Стандартная практика для скриптов “как для маленькой программы”:

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

Разберем.

  • -e: выйти при ошибке. Но помним про исключения (команды в if/while, части &&/||, и т.п.).
  • -u: ошибка при обращении к неинициализированной переменной.
  • pipefail: учитываем ошибку в пайплайне.
  • IFS=$'\n\t': уменьшает риск “случайного” разбиения по пробелам/табам при for/read (при правильном quoting это не обязательно, но как усилитель — полезно).

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


Аргументы: как парсить правильно и предсказуемо

1) Минимальный принцип: “каждый флаг — это контракт”

Сконструируйте интерфейс скрипта:

  • Какие обязательные аргументы?
  • Какие значения — строки, числа, пути?
  • Есть ли флаги типа --dry-run, --verbose, --timeout?
  • Допускаются ли повторения (--tag многократно)?
  • Что делать, если пользователь передал неправильное?

Эта логика должна отражаться в коде: валидация важна так же, как и парсинг.


2) Используем getopts для коротких опций

getopts удобен для -m, -p, -v и прочих коротких. Он не любит длинные опции, но для скриптов с минимальным интерфейсом — отличный вариант.

Пример: допустим, у нас -m MODE, -p PATH, -v.

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

mode="auto"
path="."

verbose=0

while getopts ":m:p:v" opt; do
  case "$opt" in
    m) mode="$OPTARG" ;;
    p) path="$OPTARG" ;;
    v) verbose=1 ;;
    \?) echo "Usage: $0 [-m mode] [-p path] [-v]" >&2; exit 2 ;;
  esac
done
shift $((OPTIND - 1))

# Остаток аргументов — позиционные
# "$@" нужно обрабатывать с quoting
for item in "$@"; do
  echo "item: $item"
done

Ключевые моменты:

  • "$opt" и "$OPTARG" — всегда quoting.
  • shift $((OPTIND - 1)) правильно переносит указатель на позиционные параметры.

3) Длинные опции --long: разумная практика через case + перебор

Если вам нужны длинные опции (--mode, --path), в bash обычно делают цикл while [[ $# -gt 0 ]], распознавая:

  • --flag (без значения),
  • --key=value,
  • --key value.

Пример “надежного” парсинга для набора флагов:

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

mode="auto"
path="."
verbose=0
timeout=""
inputs=()

usage() {
  cat >&2 <<'EOF'
Usage: script.sh [options] [--] <inputs...>

Options:
  --mode <auto|strict>   Режим работы
  --path <path>          Путь к ресурсам
  --timeout <seconds>    Таймаут операции
  --verbose              Подробный вывод
EOF
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --mode)
      [[ $# -ge 2 ]] || { echo "Missing value for --mode" >&2; usage; exit 2; }
      mode="$2"
      shift 2
      ;;
    --mode=*)
      mode="${1#*=}"
      shift 1
      ;;
    --path)
      [[ $# -ge 2 ]] || { echo "Missing value for --path" >&2; usage; exit 2; }
      path="$2"
      shift 2
      ;;
    --path=*)
      path="${1#*=}"
      shift 1
      ;;
    --timeout)
      [[ $# -ge 2 ]] || { echo "Missing value for --timeout" >&2; usage; exit 2; }
      timeout="$2"
      shift 2
      ;;
    --timeout=*)
      timeout="${1#*=}"
      shift 1
      ;;
    --verbose)
      verbose=1
      shift 1
      ;;
    --)
      shift
      # Остаток — позиционные входы, даже если начинается с '-'
      while [[ $# -gt 0 ]]; do
        inputs+=("$1")
        shift
      done
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      # Не распознали флаг — либо позиционные, либо ошибка
      inputs+=("$1")
      shift 1
      ;;
  esac
done

# Валидации
case "$mode" in
  auto|strict) ;;
  *) echo "Invalid --mode: $mode" >&2; exit 2 ;;
esac

if [[ -n "$timeout" ]]; then
  # Число: проверим что это целое >= 0
  [[ "$timeout" =~ ^[0-9]+$ ]] || { echo "--timeout must be a non-negative integer" >&2; exit 2; }
fi

# Для наглядности
(( verbose )) && echo "mode=$mode path=$path timeout=$timeout inputs=${#inputs[@]}"

Заметки:

  • Обработку “--” полезно предусмотреть: это стандарт POSIX — после -- значения считаются позиционными и не трактуются как флаги.
  • Используйте массив для входов (inputs=()), иначе позиционные аргументы с пробелами/спецсимволами могут стать проблемой.
  • Валидация лучше отдельным блоком (или функцией validate_*).

Безопасные переменные и quoting: что именно делать

1) Quoting — по умолчанию

Практическое правило:

  • Всегда криша/склейка: "$var" при подстановке.
  • Всегда при использовании параметров команд: cmd "$var".
  • При тестах в [[ ... ]]: тоже quoting: [[ -n "$var" ]].

Пример частой ошибки:

code
if [ -z $mode ]; then
  ...
fi

Если mode пустой или unset, это может дать ошибку синтаксиса. Правильно:

code
if [[ -z "${mode:-}" ]]; then
  ...
fi

"${mode:-}" — безопасное значение по умолчанию (в отличие от $mode при set -u).


2) Когда нужен “неочевидный” default: ${var:-default} vs ${var:?message}

  • ${var:-default} — мягко: если var пустой или не задан, подставим default.
  • ${var:?message} — жестко: если var не задан или пустой, скрипт упадет с message.

В сочетании с set -u это мощный механизм “контрактов”.

Пример: обязательный параметр:

code
required_path="${path:?--path is required}"

3) read и массивы: не ломаем пробелы

Если нужно прочитать строку целиком (включая пробелы), используйте read -r и правильный IFS.

code
while IFS= read -r line; do
  echo "LINE: $line"
done < <(some_command)

-r отключает интерпретацию backslash как escape-секвенций.


Функции: как писать их “как в коде”, а не “как в заметках”

1) Функции должны быть маленькими и с явными входами/выходами

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

code
parse_seconds() {
  local s="$1"
  [[ "$s" =~ ^[0-9]+$ ]] || return 1
  printf '%s\n' "$s"
}

Тут функция:

  • принимает s,
  • валидирует,
  • возвращает ошибку через return 1,
  • при успехе выводит результат через stdout.

Так проще использовать и тестировать.


2) Ошибки: return вместо exit внутри функций

В большинстве случаев exit внутри функции делает управление менее предсказуемым (вы не всегда знаете, откуда функция вызвана). Поэтому разумнее:

  • функция возвращает код,
  • вызывающая сторона решает, что делать дальше.

Пример:

code
ensure_file_exists() {
  local f="$1"
  [[ -f "$f" ]] || { echo "File not found: $f" >&2; return 1; }
}

# Вызов:
if ! ensure_file_exists "$path"; then
  exit 1
fi

При set -e поведение if ! func; then будет корректным: вы контролируете ветку ошибки.


3) trap для централизованной обработки

Для реальных скриптов полезно перехватывать аварийные завершения и показывать контекст. Например, логировать строку ошибки и команду.

code
on_error() {
  local exit_code=$?
  local line_no=$1
  echo "Error: command failed with exit code $exit_code at line $line_no" >&2
}

trap 'on_error $LINENO' ERR

С set -euo pipefail такой подход особенно полезен: иначе вы получаете “скрипт умер”, но без точного места.


Обработка ошибок без паники: практики, которые работают

1) Там, где ошибка — это ожидаемое условие, обрабатывайте явно

Не пытайтесь “убить” set -e постоянными || true. Если ошибка ожидаема — обработайте её как ветвь.

Например, “файл может отсутствовать”:

code
if [[ -f "$file" ]]; then
  cat "$file"
else
  echo "No such file, skipping: $file" >&2
fi

Если же ошибка действительно должна считаться фатальной — пусть set -e поможет, но убедитесь, что команды и контекст корректны.


2) Используйте &&/|| только осознанно

Пайплайны и логические операторы имеют свою семантику с set -e. Если вы используете:

code
cmd && echo "ok" || echo "fail"

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

code
if cmd; then
  echo "ok"
else
  echo "fail" >&2
fi

3) Валидация входных данных до выполнения опасных операций

Пример: вы собираетесь удалять файлы по шаблону. Сначала проверьте:

  • что каталог существует,
  • что это именно каталог, а не файл,
  • что он не совпадает с / или пустым значением,
  • что включен режим подтверждения (--force).

Даже в bash это можно сделать дисциплинированно и безопасно.


Типичные подводные камни (короткий чеклист)

  1. Забытый quoting: $var вместо "$var".
  2. Пустые значения при set -u: используйте ${var:-} или ${var:?}.
  3. Пайплайны без pipefail: скрытые ошибки.
  4. Парсинг аргументов без проверки “что если нет значения”.
  5. Игнор кода возврата функций при set -e.
  6. exit внутри функций вместо return.
  7. Некорректные read/циклы, которые разбивают строку по пробелам.

Шаблон-скелет bash-скрипта: минимально надежный каркас для повторного использования

Ниже — скелет, который объединяет:

  • строгие режимы,
  • разбор аргументов (длинные опции),
  • валидацию,
  • функции,
  • централизованный trap,
  • скелет main-логики.

Можно вставлять и адаптировать.

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

trap 'on_error $LINENO' ERR

on_error() {
  local line_no=$1
  local exit_code=$?
  echo "Error: exit code $exit_code at line $line_no" >&2
}

usage() {
  cat >&2 <<'EOF'
Usage: myscript.sh [options] [--] <inputs...>

Options:
  --mode <auto|strict>
  --path <path>
  --timeout <seconds>
  --verbose
  -h, --help
EOF
}

validate_mode() {
  local m="$1"
  case "$m" in
    auto|strict) return 0 ;;
    *) echo "Invalid --mode: $m" >&2; return 1 ;;
  esac
}

parse_timeout() {
  local t="$1"
  [[ -n "$t" ]] || { echo "timeout is empty" >&2; return 1; }
  [[ "$t" =~ ^[0-9]+$ ]] || { echo "--timeout must be a non-negative integer" >&2; return 1; }
  return 0
}

# --- defaults ---

Войдите, чтобы поставить лайк и оставить комментарий.

Автор

$ sudo teach IT

Курс по теме

Изучаем bash

Разработка скриптов на bash

Бесплатно
Открыть курс

Продолжите обучение

Все курсы
Python – для начинающих!

Python – для начинающих!

С нуля до профессионального уровня. Подходит для всех. Учитесь каждый день и овладейте самым популярным языком программирования.

Перейти к курсу
Solidity DLC - поймай, если сможешь

Solidity DLC - поймай, если сможешь

Аудит смарт-контрактов на реальных взломах. The DAO, Parity, Wormhole, Ronin, Euler и ещё 7 разборов: что пошло не так, сколько потеряли и что бы спасло. Атаки и защиты на Foundry, план входа в профессию аудитора.

Перейти к курсу
Ботостроение Telegram

Ботостроение Telegram

Лёгкий, быстрый и доступный способ познакомиться с миром ботостроения в Telegram. Видео, конспекты, практика и помощь – всё у нас на курсе.

Перейти к курсу
Solidity pro - всё или ничего

Solidity pro - всё или ничего

Как пишут контракты, которые работают в основной сети. Наследование и OpenZeppelin, паттерны продакшена, оптимизация газа, ABI, delegatecall и подписи. 7 модулей, 33 урока, а в финале собственное dApp: мини-DAO с токеном, голосованием и казной.

Перейти к курсу

Другие статьи

Работа с Gantt-логикой задач: как проектировать дедлайны, ретраи и ограничения в асинхронных сервисах
работа

Работа с Gantt-логикой задач: как проектировать дедлайны, ретраи и ограничения в асинхронных сервисах

Покажем практический шаблон управления временем выполнения: дедлайн на весь запрос, отмена вложенных задач, ретраи с backoff и защита от каскадных зависаний при сетевых ошибках.

17 сентября 2026 г.
1840
Пишем корректные bash-скрипты: обработка ошибок, строгие режимы и безопасные переменные
пишем

Пишем корректные bash-скрипты: обработка ошибок, строгие режимы и безопасные переменные

Покажем best practices: set -euo pipefail, ловля ошибок, корректное quoting и проверка входных параметров. Разберём мини-проект с устойчивым поведением.

24 июля 2026 г.
990
Как выучить программирование самому и бесплатно: с чего начать и в каком порядке
выучить

Как выучить программирование самому и бесплатно: с чего начать и в каком порядке

Разберём понятный маршрут для новичка: как выбрать первый язык, что учить в первую очередь, какие мини-проекты делать и как не забросить занятия. Дам бесплатные ориентиры и план, чтобы знания складывались в портфолио.

21 сентября 2026 г.
1970
Как начать с HTML и сделать первую страницу: пошагово и без сложных слов
html

Как начать с HTML и сделать первую страницу: пошагово и без сложных слов

Разберём, что нужно новичку, чтобы за 1–2 вечера собрать свою первую веб-страницу: структура HTML, базовые теги, ссылки и картинки, плюс простой чек-лист, как понять, что страница работает правильно.

2 ноября 2026 г.
20
Cargo для продакшена: workspaces, фичи и стратегия версий зависимостей
cargo

Cargo для продакшена: workspaces, фичи и стратегия версий зависимостей

Разберём, как организовать Rust workspace, управлять optional features, фиксировать версии библиотек и уменьшать “дрейф” зависимостей при командной разработке и CI.

18 сентября 2026 г.
1960
Что такое HTML и чем он отличается от CSS — объясню простыми словами и на примерах
такое

Что такое HTML и чем он отличается от CSS — объясню простыми словами и на примерах

Разберём, из чего состоит веб-страница: что делает HTML (смысл и структура) и что делает CSS (оформление). В конце соберёте мини-страницу и поймёте, что нужно учить в каком порядке.

30 октября 2026 г.
130

Комментарии

Станьте первым в обсуждении

Поделитесь мнением о статье или задайте вопрос автору

Содержание

Почему bash-скрипты ломаются: реальные причины1) Аргументы и пробелы: “оно же строка”2) Парсинг аргументов: “пробовали, но как-то работает”3) set -e без понимания семантики4) Ошибки в функциях “не те, где вы думаете”5) Пайплайны и скрытые ошибкиБазовый набор “безопасности” для bash: set -euo pipefail и дисциплинаАргументы: как парсить правильно и предсказуемо1) Минимальный принцип: “каждый флаг — это контракт”2) Используем getopts для коротких опций3) Длинные опции --long: разумная практика через case + переборБезопасные переменные и quoting: что именно делать1) Quoting — по умолчанию2) Когда нужен “неочевидный” default: ${var:-default} vs ${var:?message}3) read и массивы: не ломаем пробелыФункции: как писать их “как в коде”, а не “как в заметках”1) Функции должны быть маленькими и с явными входами/выходами2) Ошибки: return вместо exit внутри функций3) trap для централизованной обработкиОбработка ошибок без паники: практики, которые работают1) Там, где ошибка — это ожидаемое условие, обрабатывайте явно2) Используйте &&/|| только осознанно3) Валидация входных данных до выполнения опасных операцийТипичные подводные камни (короткий чеклист)Шаблон-скелет bash-скрипта: минимально надежный каркас для повторного использования