Home » Чтение аргументов командной строки в shell-скриптах
Чтение аргументов командной строки в shell-скриптах

Чтение аргументов командной строки в 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.


В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.

Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.

Leave a reply

Your email address will not be published. Required fields are marked