Освоение разбора аргументов Bash для мощных скриптов
Разбирайте аргументы Bash с помощью позиционных параметров, getopts, циклов для длинных опций, значений по умолчанию и понятных сообщений об ошибках.
Освоение разбора аргументов Bash для мощных скриптов
Аргументы командной строки делают ваши Bash-скрипты гибкими. Когда разбор аргументов Bash слабый, вам приходится редактировать переменные внутри скрипта каждый раз, когда нужен другой файл, хост или режим.
Это руководство покажет вам, как обрабатывать позиционные аргументы, короткие опции с getopts и длинные опции с циклом while/case. Цель — скрипт, который отклоняет некорректный ввод на раннем этапе и точно сообщает, как его запустить.
Основы: Позиционные аргументы
Прежде чем переходить к флагам и именованным опциям, важно понять, как Bash обрабатывает простые последовательные аргументы.
| Переменная | Описание | Пример при вызове ./script.sh foo bar |
|---|---|---|
$0 |
Имя самого скрипта | ./script.sh |
$1, $2, ... |
Первый, второй и т.д. позиционный аргумент | foo, bar |
$# |
Количество позиционных аргументов | 2 |
$@ |
Все позиционные аргументы, обрабатываемые как отдельные строки | foo bar |
$* |
Все позиционные аргументы, обрабатываемые как одна строка | foo bar |
Пример: Простая проверка позиционных аргументов
#!/bin/bash
if [ "$#" -ne 2 ]; then
echo "Использование: $0 <исходный_файл> <целевая_директория>"
exit 1
fi
SOURCE="$1"
DESTINATION="$2"
echo "Копирование $SOURCE в $DESTINATION..."
# cp "$SOURCE" "$DESTINATION"
Стандартный разбор с getopts для коротких опций
Для профессиональных скриптов, требующих опции, такие как -v (подробный вывод) или -f <файл>, встроенная утилита getopts является каноническим и наиболее надежным методом в чистом Bash. Она специально разработана для коротких (односимвольных) опций.
Как работает getopts
getopts последовательно читает опции, устанавливая указанную переменную (обычно OPT) в найденную опцию и помещая значение любого аргумента (если требуется) в переменную OPTARG.
- Строка опций (OPTSTRING): Определяет допустимые опции. Если опция требует аргумента (например,
-f имя_файла), после символа ставится двоеточие (:). Пример:vho:допускает-v,-hи-o, который требует значение. OPTIND: Внутренний индекс Bash, отслеживающий следующий аргумент для обработки. Он должен быть инициализирован в 1 (значение по умолчанию, но иногда сбрасывается в сложных скриптах).
Практический шаблон getopts
Этот шаблон обрабатывает три опции: -v (флаг), -h (флаг) и -f (опция, требующая значение).
#!/bin/bash
# --- Значения по умолчанию ---
VERBOSE=0
FILENAME="default.txt"
# --- Функция использования ---
usage() {
echo "Использование: $0 [-v] [-h] [-f <файл>] <входные_данные>"
exit 1
}
# --- Цикл разбора аргументов ---
while getopts ":vhf:" OPT; do
case "$OPT" in
v)
VERBOSE=1
echo "Подробный режим включен."
;;
h)
usage
;;
f)
# OPTARG содержит аргумент, переданный для -f
FILENAME="$OPTARG"
echo "Имя выходного файла установлено: $FILENAME"
;;
\?)
# Обрабатывает неизвестные опции
echo "Ошибка: Неизвестная опция -$OPTARG" >&2
usage
;;
:)
# Обрабатывает отсутствие аргументов для опций, требующих их (например, -f без имени файла)
echo "Ошибка: Опция -$OPTARG требует аргумент." >&2
usage
;;
esac
done
# --- Сдвиг позиционных аргументов ---
# После завершения getopts OPTIND содержит индекс первого неопционного аргумента.
# Мы используем shift, чтобы отбросить все разобранные опции, оставив только позиционные аргументы ($1, $2 и т.д.).
shift $((OPTIND - 1))
# Проверка наличия обязательного позиционного аргумента (INPUT)
INPUT_DATA="$1"
if [ -z "$INPUT_DATA" ]; then
echo "Ошибка: Требуются входные данные."
usage
fi
echo "---"
echo "Обработка ввода: $INPUT_DATA"
echo "Статус подробного вывода: $VERBOSE"
Совет: Всегда используйте
shift $((OPTIND - 1))сразу после циклаwhile getopts, чтобы чисто отделить опции от оставшихся позиционных аргументов.
Обработка длинных опций, таких как --option
Встроенный getopts в чистом Bash поддерживает только короткие опции. Для обработки современных длинных опций (например, --verbose, --output-file=data.log) необходимо реализовать собственный цикл разбора с помощью while и case с командой shift.
Этот метод требует более явного управления аргументами.
Пользовательский парсер длинных опций
#!/bin/bash
# --- Значения по умолчанию ---
VERBOSE=0
OUTPUT_FILE=""
# Пользовательская функция для отображения использования (опущена для краткости)
# usage() { ... }
while [ "$#" -gt 0 ]; do
case "$1" in
--verbose)
VERBOSE=1
shift
;;
--output-file)
# Требуется два сдвига: один для флага, один для значения
if [ -z "${2:-}" ]; then
echo "Ошибка: --output-file требует значение."
exit 1
fi
OUTPUT_FILE="$2"
shift 2
;;
--help)
usage
;;
-*)
echo "Ошибка: Неизвестная опция $1" >&2
exit 1
;;
*)
# Найден первый позиционный аргумент, прекращаем разбор опций
break
;;
esac
done
# Оставшиеся аргументы теперь доступны как $1, $2 и т.д.
# ... Логика скрипта продолжается ...
if [ "$OUTPUT_FILE" ]; then
echo "Данные перенаправлены в $OUTPUT_FILE"
fi
Если ваш скрипт принимает позиционные аргументы после опций, продолжайте разбор до тех пор, пока не встретите -- или первый неопционный аргумент. Распространенное соглашение — позволить -- означать "прекратить разбор опций сейчас":
while [ "$#" -gt 0 ]; do
case "$1" in
--)
shift
break
;;
--verbose)
VERBOSE=1
shift
;;
*)
break
;;
esac
done
Продвинутый: Обработка длинных опций вида ключ-значение (--key=value)
Если вы предпочитаете стандартный стиль UNIX, где аргументы передаются через знак равенства, вам нужно использовать подстановку параметров для разделения аргумента.
while [ "$#" -gt 0 ]; do
case "$1" in
--limit=*)
LIMIT_VAL="${1#*=}" # Удалить все до и включая '='
echo "Лимит установлен: $LIMIT_VAL"
shift
;;
# ... другие опции ...
esac
done
Лучшие практики для надежного написания скриптов
A. Комбинирование коротких и длинных опций
Для максимальной гибкости опытные скриптеры часто комбинируют надежность getopts для коротких опций с пользовательским циклом while/case для длинных опций. Цикл длинных опций выполняется первым, потребляя длинные флаги, а затем оставшиеся аргументы (включая короткие опции) обрабатываются getopts.
Однако более чистый и распространенный шаблон — обрабатывать все аргументы в одном надежном пользовательском цикле, который ищет как -o, так и --option, соответствующим образом сдвигая.
B. Обработка ошибок и использование
Всегда предоставляйте четкую функцию usage, которая объясняет обязательные аргументы и опции скрипта. Эта функция должна вызываться, когда:
- Пользователь запрашивает справку (например,
-hили--help). - Отсутствует обязательный аргумент.
- Указана недопустимая или неизвестная опция.
Убедитесь, что скрипт завершается с ненулевым статусом при ошибке (exit 1) и выводит сообщения об ошибках в стандартный поток ошибок (>&2).
C. Значения по умолчанию
Инициализируйте все конфигурационные переменные в начале скрипта с разумными значениями по умолчанию. Это делает ваш скрипт предсказуемым, даже если не переданы никакие необязательные аргументы.
# Всегда инициализируйте переменные перед разбором
LOG_LEVEL="info"
FORCE=0
Вывод
Используйте позиционные аргументы для простых обязательных значений, getopts для переносимых коротких опций и пользовательский цикл while/case, когда вам нужны длинные опции. Тестируйте отсутствующие значения, неизвестные флаги и аргументы с пробелами, прежде чем доверять скрипту в автоматизации.