- Home »

Чтение аргументов командной строки в shell-скриптах
Если ты когда-нибудь писал bash-скрипты для автоматизации задач на серверах, то наверняка сталкивался с необходимостью передачи параметров через командную строку. Это одна из базовых фундаментальных вещей, которая превращает простой скрипт в полноценный инструмент администрирования. Сегодня разберём все способы чтения аргументов в shell-скриптах — от простейших до продвинутых техник с обработкой флагов, валидацией и error handling’ом.
Почему это важно? Представь, что у тебя есть скрипт для бэкапа базы данных. Без аргументов командной строки тебе придётся каждый раз редактировать код, чтобы изменить имя базы или путь к файлу. С правильной обработкой параметров твой скрипт становится универсальным и переиспользуемым. Это экономит время и снижает вероятность ошибок при администрировании VPS или выделенных серверов.
Как это работает: основы обработки аргументов
В bash аргументы командной строки автоматически помещаются в специальные переменные. Это встроенная функциональность shell’а, которая работает без дополнительных библиотек:
- $0 — имя скрипта
- $1, $2, $3… — позиционные параметры
- $# — количество переданных аргументов
- $@ — все аргументы как отдельные слова
- $* — все аргументы как одна строка
Простейший пример:
#!/bin/bash
echo "Имя скрипта: $0"
echo "Первый аргумент: $1"
echo "Второй аргумент: $2"
echo "Всего аргументов: $#"
echo "Все аргументы: $@"
Если запустить этот скрипт с параметрами ./script.sh database backup /tmp
, получим:
Имя скрипта: ./script.sh
Первый аргумент: database
Второй аргумент: backup
Всего аргументов: 3
Все аргументы: database backup /tmp
Продвинутые техники: getopt и getopts
Для серьёзных скриптов нужна обработка опций в стиле GNU. Здесь есть два основных подхода: встроенная команда getopts
и внешняя утилита getopt
.
Использование getopts
getopts
— это встроенная команда bash, которая идеально подходит для обработки коротких опций (-h, -v, -f file). Вот реальный пример скрипта для мониторинга сервера:
#!/bin/bash
# Значения по умолчанию
HOST="localhost"
PORT="22"
VERBOSE=false
HELP=false
# Обработка опций
while getopts "h:p:vh" opt; do
case $opt in
h) HOST="$OPTARG" ;;
p) PORT="$OPTARG" ;;
v) VERBOSE=true ;;
h) HELP=true ;;
\?) echo "Неверная опция: -$OPTARG" >&2; exit 1 ;;
:) echo "Опция -$OPTARG требует аргумент" >&2; exit 1 ;;
esac
done
# Сдвиг позиционных параметров
shift $((OPTIND-1))
if [ "$HELP" = true ]; then
echo "Использование: $0 [-h host] [-p port] [-v] [command]"
exit 0
fi
if [ "$VERBOSE" = true ]; then
echo "Подключение к $HOST:$PORT"
fi
# Остальные аргументы доступны как $1, $2, etc.
COMMAND="$1"
Использование getopt для длинных опций
Для поддержки длинных опций (–help, –verbose) нужна внешняя утилита getopt
. Она более мощная, но требует больше кода:
#!/bin/bash
# Парсинг опций
TEMP=$(getopt -o h:p:vh --long host:,port:,verbose,help -n 'script.sh' -- "$@")
if [ $? != 0 ]; then
echo "Ошибка в опциях" >&2
exit 1
fi
eval set -- "$TEMP"
# Значения по умолчанию
HOST="localhost"
PORT="22"
VERBOSE=false
while true; do
case "$1" in
-h|--host) HOST="$2"; shift 2 ;;
-p|--port) PORT="$2"; shift 2 ;;
-v|--verbose) VERBOSE=true; shift ;;
--help) echo "Помощь"; exit 0 ;;
--) shift; break ;;
*) echo "Внутренняя ошибка!"; exit 1 ;;
esac
done
# Оставшиеся аргументы
echo "Хост: $HOST, Порт: $PORT, Verbose: $VERBOSE"
echo "Остальные аргументы: $@"
Практические примеры и кейсы
Скрипт для бэкапа базы данных
Вот реальный пример скрипта, который я использую для автоматизации бэкапов:
#!/bin/bash
# Функция вывода помощи
show_help() {
cat << EOF
Использование: $0 [ОПЦИИ] DATABASE
Опции:
-h, --host HOST Хост базы данных (по умолчанию: localhost)
-u, --user USER Пользователь базы данных
-p, --password PASS Пароль (небезопасно!)
-o, --output DIR Директория для сохранения бэкапа
-c, --compress Сжать результат с помощью gzip
-v, --verbose Подробный вывод
--help Показать эту справку
Пример:
$0 -u admin -o /backups --compress mydb
EOF
}
# Значения по умолчанию
HOST="localhost"
USER="root"
PASSWORD=""
OUTPUT_DIR="/tmp"
COMPRESS=false
VERBOSE=false
# Обработка аргументов
while [[ $# -gt 0 ]]; do
case $1 in
-h|--host)
HOST="$2"
shift 2
;;
-u|--user)
USER="$2"
shift 2
;;
-p|--password)
PASSWORD="$2"
shift 2
;;
-o|--output)
OUTPUT_DIR="$2"
shift 2
;;
-c|--compress)
COMPRESS=true
shift
;;
-v|--verbose)
VERBOSE=true
shift
;;
--help)
show_help
exit 0
;;
-*)
echo "Неизвестная опция: $1" >&2
exit 1
;;
*)
DATABASE="$1"
shift
;;
esac
done
# Проверка обязательных параметров
if [ -z "$DATABASE" ]; then
echo "Ошибка: не указана база данных" >&2
show_help
exit 1
fi
# Основная логика
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$OUTPUT_DIR/${DATABASE}_$TIMESTAMP.sql"
if [ "$VERBOSE" = true ]; then
echo "Создание бэкапа базы: $DATABASE"
echo "Хост: $HOST"
echo "Пользователь: $USER"
echo "Файл: $BACKUP_FILE"
fi
# Создание бэкапа
if mysqldump -h "$HOST" -u "$USER" ${PASSWORD:+-p"$PASSWORD"} "$DATABASE" > "$BACKUP_FILE"; then
if [ "$COMPRESS" = true ]; then
gzip "$BACKUP_FILE"
BACKUP_FILE="${BACKUP_FILE}.gz"
fi
echo "Бэкап успешно создан: $BACKUP_FILE"
else
echo "Ошибка при создании бэкапа" >&2
exit 1
fi
Скрипт мониторинга сервера
Ещё один полезный пример — скрипт для проверки состояния сервера:
#!/bin/bash
# Функция для проверки аргументов
check_requirements() {
if ! command -v ss &> /dev/null; then
echo "Требуется утилита ss (из пакета iproute2)" >&2
exit 1
fi
}
# Обработка аргументов
SERVICES=()
CHECK_DISK=false
CHECK_MEMORY=false
CHECK_LOAD=false
OUTPUT_FORMAT="text"
while [[ $# -gt 0 ]]; do
case $1 in
-s|--service)
SERVICES+=("$2")
shift 2
;;
-d|--disk)
CHECK_DISK=true
shift
;;
-m|--memory)
CHECK_MEMORY=true
shift
;;
-l|--load)
CHECK_LOAD=true
shift
;;
-f|--format)
OUTPUT_FORMAT="$2"
shift 2
;;
*)
echo "Неизвестная опция: $1" >&2
exit 1
;;
esac
done
check_requirements
# Проверка сервисов
if [ ${#SERVICES[@]} -gt 0 ]; then
echo "=== Состояние сервисов ==="
for service in "${SERVICES[@]}"; do
if systemctl is-active --quiet "$service"; then
echo "✓ $service: активен"
else
echo "✗ $service: неактивен"
fi
done
fi
# Остальные проверки...
if [ "$CHECK_DISK" = true ]; then
echo "=== Использование диска ==="
df -h | grep -E "^/dev/"
fi
Сравнение методов обработки аргументов
Метод | Простота | Функциональность | Длинные опции | Переносимость | Рекомендации |
---|---|---|---|---|---|
Позиционные параметры ($1, $2…) | Очень простая | Базовая | Нет | Максимальная | Простые скрипты |
getopts | Средняя | Хорошая | Нет | Встроенная функция | Большинство случаев |
getopt | Сложная | Полная | Да | Требует внешнюю утилиту | Сложные скрипты |
Ручной парсинг | Сложная | Любая | Да | Максимальная | Специфичные случаи |
Продвинутые техники и хитрости
Работа с файлами конфигурации
Часто полезно комбинировать аргументы командной строки с файлами конфигурации:
#!/bin/bash
# Загрузка конфигурации по умолчанию
CONFIG_FILE="/etc/myapp/config.conf"
if [ -f "$CONFIG_FILE" ]; then
source "$CONFIG_FILE"
fi
# Пользовательская конфигурация
USER_CONFIG="$HOME/.myapp/config"
if [ -f "$USER_CONFIG" ]; then
source "$USER_CONFIG"
fi
# Аргументы командной строки переопределяют конфигурацию
while [[ $# -gt 0 ]]; do
case $1 in
-c|--config)
source "$2"
shift 2
;;
-h|--host)
HOST="$2"
shift 2
;;
# остальные опции...
esac
done
Валидация и обработка ошибок
Хороший скрипт должен проверять корректность входных данных:
#!/bin/bash
validate_port() {
local port=$1
if ! [[ "$port" =~ ^[0-9]+$ ]] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
echo "Ошибка: неверный номер порта: $port" >&2
return 1
fi
}
validate_host() {
local host=$1
if ! [[ "$host" =~ ^[a-zA-Z0-9.-]+$ ]]; then
echo "Ошибка: неверное имя хоста: $host" >&2
return 1
fi
}
validate_email() {
local email=$1
if ! [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
echo "Ошибка: неверный email: $email" >&2
return 1
fi
}
# Использование
while [[ $# -gt 0 ]]; do
case $1 in
-p|--port)
validate_port "$2" || exit 1
PORT="$2"
shift 2
;;
-h|--host)
validate_host "$2" || exit 1
HOST="$2"
shift 2
;;
-e|--email)
validate_email "$2" || exit 1
EMAIL="$2"
shift 2
;;
esac
done
Интересные факты и нестандартные применения
Динамическая обработка опций
Можно создавать скрипты, которые динамически определяют доступные опции:
#!/bin/bash
# Массив доступных команд
declare -A COMMANDS=(
["backup"]="Создать резервную копию"
["restore"]="Восстановить из резервной копии"
["status"]="Показать статус"
["cleanup"]="Очистить временные файлы"
)
show_commands() {
echo "Доступные команды:"
for cmd in "${!COMMANDS[@]}"; do
echo " $cmd - ${COMMANDS[$cmd]}"
done
}
# Проверка, что команда существует
if [ -z "$1" ] || [ -z "${COMMANDS[$1]}" ]; then
echo "Ошибка: команда не указана или не существует" >&2
show_commands
exit 1
fi
COMMAND="$1"
shift
# Теперь обрабатываем специфичные для команды опции
case "$COMMAND" in
"backup")
# Опции для backup
;;
"restore")
# Опции для restore
;;
esac
Автодополнение для bash
Можно создать автодополнение для твоего скрипта:
# Файл /etc/bash_completion.d/myscript
_myscript_completion() {
local cur prev opts
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts="-h --help -v --verbose -f --file -u --user"
case ${prev} in
-f|--file)
COMPREPLY=( $(compgen -f -- ${cur}) )
return 0
;;
-u|--user)
COMPREPLY=( $(compgen -u -- ${cur}) )
return 0
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
}
complete -F _myscript_completion myscript
Интеграция с другими инструментами
Использование с jq для JSON
Современные скрипты часто работают с JSON API:
#!/bin/bash
API_URL=""
OUTPUT_FORMAT="json"
FILTER=""
while [[ $# -gt 0 ]]; do
case $1 in
-u|--url)
API_URL="$2"
shift 2
;;
-f|--format)
OUTPUT_FORMAT="$2"
shift 2
;;
-q|--query)
FILTER="$2"
shift 2
;;
esac
done
# Получение данных
response=$(curl -s "$API_URL")
# Обработка в зависимости от формата
case "$OUTPUT_FORMAT" in
"json")
if [ -n "$FILTER" ]; then
echo "$response" | jq "$FILTER"
else
echo "$response" | jq .
fi
;;
"table")
echo "$response" | jq -r '.[] | [.name, .status, .created] | @tsv' | column -t
;;
"csv")
echo "$response" | jq -r '.[] | [.name, .status, .created] | @csv'
;;
esac
Интеграция с systemd
Скрипты с аргументами отлично работают в systemd сервисах:
# /etc/systemd/system/mybackup.service
[Unit]
Description=Database Backup Service
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh --database mydb --output /backups --compress
User=backup
Group=backup
[Install]
WantedBy=multi-user.target
Автоматизация и CI/CD
Правильно написанные скрипты с аргументами легко интегрируются в CI/CD пайплайны:
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Deploy to staging
run: |
./deploy.sh \
--environment staging \
--host ${{ secrets.STAGING_HOST }} \
--user ${{ secrets.STAGING_USER }} \
--key ${{ secrets.STAGING_KEY }} \
--branch ${{ github.ref }}
Отладка и логирование
Для сложных скриптов полезно добавить отладочную информацию:
#!/bin/bash
DEBUG=false
LOG_FILE="/var/log/myscript.log"
# Функция логирования
log() {
local level=$1
shift
local message="$@"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}
debug() {
if [ "$DEBUG" = true ]; then
log "DEBUG" "$@"
fi
}
# Обработка аргументов
while [[ $# -gt 0 ]]; do
case $1 in
--debug)
DEBUG=true
shift
;;
--log-file)
LOG_FILE="$2"
shift 2
;;
*)
# остальные опции
;;
esac
done
debug "Скрипт запущен с аргументами: $@"
debug "Файл логов: $LOG_FILE"
Безопасность и лучшие практики
- Никогда не передавай пароли через аргументы — они видны в списке процессов
- Используй переменные окружения для чувствительных данных
- Валидируй все входные данные — никогда не доверяй пользовательскому вводу
- Экранируй специальные символы при работе с пользовательскими данными
- Используй
set -euo pipefail
для строгой обработки ошибок
#!/bin/bash
set -euo pipefail # Строгий режим
# Безопасное чтение пароля
read_password() {
local password
echo -n "Пароль: "
read -s password
echo
echo "$password"
}
# Экранирование специальных символов
escape_sql() {
local input="$1"
echo "$input" | sed "s/'/\\'/g"
}
# Пример использования
while [[ $# -gt 0 ]]; do
case $1 in
--password-file)
if [ ! -r "$2" ]; then
echo "Ошибка: файл пароля недоступен" >&2
exit 1
fi
PASSWORD=$(cat "$2")
shift 2
;;
--database)
DATABASE=$(escape_sql "$2")
shift 2
;;
esac
done
Заключение и рекомендации
Правильная обработка аргументов командной строки — это основа профессиональных shell-скриптов. Для большинства задач рекомендую использовать встроенную команду getopts
— она покрывает 90% случаев использования и не требует внешних зависимостей.
Используй getopt
только если действительно нужны длинные опции или сложная логика обработки. Для простых скриптов с 1-2 параметрами вполне достаточно позиционных аргументов.
Основные принципы:
- Всегда добавляй опцию
--help
с описанием всех параметров - Используй значения по умолчанию для необязательных параметров
- Валидируй входные данные и выдавай понятные сообщения об ошибках
- Комбинируй файлы конфигурации с аргументами командной строки
- Не забывай про безопасность — никогда не передавай пароли через аргументы
Эти техники особенно важны при работе с серверной инфраструктурой, где автоматизация и надёжность скриптов критически важны. Правильно написанные скрипты с аргументами сэкономят тебе массу времени при администрировании серверов и помогут избежать ошибок при повторяющихся задачах.
Дополнительные материалы можно найти в официальной документации bash: GNU Bash Manual и Advanced Bash-Scripting Guide.
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.