Home » Аргументы командной строки в Python — как обрабатывать входные параметры
Аргументы командной строки в Python — как обрабатывать входные параметры

Аргументы командной строки в Python — как обрабатывать входные параметры

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

Сегодня разберёмся, как правильно обрабатывать аргументы командной строки в Python, чтобы твои скрипты стали по-настоящему гибкими и удобными. Покажу три основных способа: от простейшего sys.argv до мощного argparse, с кучей примеров и готовых решений, которые можно сразу использовать в продакшне.

Почему это важно для серверного администрирования

Когда ты разворачиваешь инфраструктуру на VPS или выделенном сервере, тебе постоянно нужны скрипты для мониторинга, бэкапов, развёртывания. И каждый такой скрипт должен быть настраиваемым без правки кода.

Представь: скрипт для проверки доступности сервисов. Сегодня тебе нужно проверить веб-сервер, завтра — базу данных, послезавтра — всё сразу, но с другим timeout. Если параметры захардкодены, придётся держать кучу версий скрипта или постоянно их править.

Три способа обработки аргументов

1. sys.argv — для простых случаев

Самый базовый способ. Модуль sys предоставляет список argv, где лежат все переданные аргументы:

#!/usr/bin/env python3
import sys

# Проверяем количество аргументов
if len(sys.argv) < 2:
    print("Использование: python script.py ")
    sys.exit(1)

hostname = sys.argv[1]
port = int(sys.argv[2]) if len(sys.argv) > 2 else 80

print(f"Проверяем {hostname}:{port}")

Плюсы: просто и быстро. Минусы: никакой валидации, сложно добавлять новые параметры, неудобно для пользователя.

2. argparse — мощь и гибкость

Стандартный модуль для серьёзных скриптов. Автоматически генерирует help, валидирует типы, поддерживает подкоманды:

#!/usr/bin/env python3
import argparse
import sys

def main():
    parser = argparse.ArgumentParser(description='Проверка доступности сервиса')
    
    # Обязательные аргументы
    parser.add_argument('hostname', help='Имя хоста или IP')
    
    # Опциональные аргументы
    parser.add_argument('-p', '--port', type=int, default=80, 
                       help='Порт для проверки (по умолчанию: 80)')
    parser.add_argument('-t', '--timeout', type=float, default=5.0,
                       help='Таймаут в секундах')
    parser.add_argument('-v', '--verbose', action='store_true',
                       help='Подробный вывод')
    parser.add_argument('--retries', type=int, default=3,
                       help='Количество попыток')
    
    args = parser.parse_args()
    
    if args.verbose:
        print(f"Проверяем {args.hostname}:{args.port}")
        print(f"Таймаут: {args.timeout}s, попыток: {args.retries}")
    
    # Здесь логика проверки
    check_service(args.hostname, args.port, args.timeout, args.retries)

def check_service(hostname, port, timeout, retries):
    # Твоя логика проверки сервиса
    pass

if __name__ == "__main__":
    main()

Теперь скрипт можно вызывать красиво:

# Минимальный вызов
python check_service.py example.com

# С дополнительными параметрами
python check_service.py example.com -p 443 -t 10 --verbose

# Получить справку
python check_service.py --help

3. click — для сложных CLI-приложений

Внешняя библиотека, которая делает создание CLI ещё проще благодаря декораторам:

# Установка: pip install click
import click
import socket

@click.command()
@click.argument('hostname')
@click.option('-p', '--port', default=80, help='Порт для проверки')
@click.option('-t', '--timeout', default=5.0, help='Таймаут в секундах')
@click.option('-v', '--verbose', is_flag=True, help='Подробный вывод')
@click.option('--retries', default=3, help='Количество попыток')
def check_service(hostname, port, timeout, verbose, retries):
    """Проверка доступности сервиса"""
    if verbose:
        click.echo(f"Проверяем {hostname}:{port}")
    
    for attempt in range(retries):
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(timeout)
            result = sock.connect_ex((hostname, port))
            sock.close()
            
            if result == 0:
                click.echo(f"✓ {hostname}:{port} доступен")
                return
            
        except Exception as e:
            if verbose:
                click.echo(f"Попытка {attempt + 1}: {e}")
    
    click.echo(f"✗ {hostname}:{port} недоступен")

if __name__ == '__main__':
    check_service()

Практические примеры для администрирования

Скрипт для мониторинга дискового пространства

#!/usr/bin/env python3
import argparse
import shutil
import os
import sys

def check_disk_space(path, threshold, unit='GB'):
    """Проверка свободного места на диске"""
    total, used, free = shutil.disk_usage(path)
    
    # Конвертация в нужные единицы
    divisor = 1024**3 if unit == 'GB' else 1024**2
    free_space = free / divisor
    
    if free_space < threshold:
        print(f"⚠️  ВНИМАНИЕ: На {path} осталось {free_space:.1f} {unit}")
        return False
    else:
        print(f"✓ {path}: {free_space:.1f} {unit} свободно")
        return True

def main():
    parser = argparse.ArgumentParser(description='Мониторинг дискового пространства')
    parser.add_argument('paths', nargs='+', help='Пути для проверки')
    parser.add_argument('-t', '--threshold', type=float, default=1.0,
                       help='Порог свободного места (по умолчанию: 1.0)')
    parser.add_argument('-u', '--unit', choices=['GB', 'MB'], default='GB',
                       help='Единицы измерения')
    parser.add_argument('--exit-on-warning', action='store_true',
                       help='Завершить с кодом ошибки при превышении порога')
    
    args = parser.parse_args()
    
    all_ok = True
    for path in args.paths:
        if not os.path.exists(path):
            print(f"❌ Путь {path} не существует")
            all_ok = False
            continue
            
        if not check_disk_space(path, args.threshold, args.unit):
            all_ok = False
    
    if not all_ok and args.exit_on_warning:
        sys.exit(1)

if __name__ == "__main__":
    main()

Использование:

# Проверить корень и /var
python disk_monitor.py / /var

# Проверить с порогом 5GB
python disk_monitor.py / /var -t 5.0

# Для использования в cron (завершится с ошибкой при проблемах)
python disk_monitor.py / /var -t 2.0 --exit-on-warning

Скрипт для бэкапа с гибкой конфигурацией

#!/usr/bin/env python3
import argparse
import subprocess
import datetime
import os
import sys

def create_backup(source, destination, compression='gzip', exclude_patterns=None):
    """Создание бэкапа с указанными параметрами"""
    timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
    
    if compression == 'gzip':
        backup_name = f"backup_{timestamp}.tar.gz"
        compress_flag = 'z'
    elif compression == 'bzip2':
        backup_name = f"backup_{timestamp}.tar.bz2"
        compress_flag = 'j'
    else:
        backup_name = f"backup_{timestamp}.tar"
        compress_flag = ''
    
    backup_path = os.path.join(destination, backup_name)
    
    # Строим команду tar
    cmd = ['tar', f'c{compress_flag}f', backup_path, '-C', os.path.dirname(source)]
    
    # Добавляем исключения
    if exclude_patterns:
        for pattern in exclude_patterns:
            cmd.extend(['--exclude', pattern])
    
    cmd.append(os.path.basename(source))
    
    try:
        print(f"Создаём бэкап: {backup_path}")
        subprocess.run(cmd, check=True)
        print(f"✓ Бэкап создан: {backup_path}")
        return backup_path
    except subprocess.CalledProcessError as e:
        print(f"❌ Ошибка создания бэкапа: {e}")
        return None

def main():
    parser = argparse.ArgumentParser(description='Создание бэкапов директорий')
    parser.add_argument('source', help='Исходная директория')
    parser.add_argument('destination', help='Директория для бэкапов')
    parser.add_argument('-c', '--compression', choices=['none', 'gzip', 'bzip2'], 
                       default='gzip', help='Тип сжатия')
    parser.add_argument('-e', '--exclude', action='append', dest='exclude_patterns',
                       help='Паттерны для исключения (можно указать несколько раз)')
    parser.add_argument('--dry-run', action='store_true',
                       help='Показать команду без выполнения')
    
    args = parser.parse_args()
    
    # Проверяем существование директорий
    if not os.path.exists(args.source):
        print(f"❌ Исходная директория {args.source} не существует")
        sys.exit(1)
    
    if not os.path.exists(args.destination):
        print(f"❌ Директория назначения {args.destination} не существует")
        sys.exit(1)
    
    if args.dry_run:
        print("Режим dry-run: команда не будет выполнена")
        print(f"Источник: {args.source}")
        print(f"Назначение: {args.destination}")
        print(f"Сжатие: {args.compression}")
        if args.exclude_patterns:
            print(f"Исключения: {', '.join(args.exclude_patterns)}")
        return
    
    create_backup(args.source, args.destination, args.compression, args.exclude_patterns)

if __name__ == "__main__":
    main()

Продвинутые техники

Группировка аргументов

Для сложных скриптов можно группировать связанные параметры:

parser = argparse.ArgumentParser()

# Группа подключения к базе данных
db_group = parser.add_argument_group('database', 'Параметры подключения к БД')
db_group.add_argument('--db-host', default='localhost')
db_group.add_argument('--db-port', type=int, default=5432)
db_group.add_argument('--db-name', required=True)
db_group.add_argument('--db-user', required=True)

# Группа логирования
log_group = parser.add_argument_group('logging', 'Настройки логирования')
log_group.add_argument('--log-level', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'], default='INFO')
log_group.add_argument('--log-file', help='Файл для записи логов')

Взаимоисключающие параметры

Иногда нужно, чтобы пользователь выбрал один из вариантов:

parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('--start', action='store_true', help='Запустить сервис')
group.add_argument('--stop', action='store_true', help='Остановить сервис')
group.add_argument('--restart', action='store_true', help='Перезапустить сервис')

Подкоманды

Для создания CLI-приложений с множеством функций:

#!/usr/bin/env python3
import argparse

def cmd_status(args):
    print(f"Статус сервиса {args.service}")

def cmd_start(args):
    print(f"Запускаем {args.service}")

def cmd_stop(args):
    print(f"Останавливаем {args.service}")

def main():
    parser = argparse.ArgumentParser(description='Управление сервисами')
    subparsers = parser.add_subparsers(dest='command', help='Доступные команды')
    
    # Подкоманда status
    status_parser = subparsers.add_parser('status', help='Показать статус')
    status_parser.add_argument('service', help='Имя сервиса')
    status_parser.set_defaults(func=cmd_status)
    
    # Подкоманда start
    start_parser = subparsers.add_parser('start', help='Запустить сервис')
    start_parser.add_argument('service', help='Имя сервиса')
    start_parser.add_argument('--wait', action='store_true', help='Ждать запуска')
    start_parser.set_defaults(func=cmd_start)
    
    # Подкоманда stop
    stop_parser = subparsers.add_parser('stop', help='Остановить сервис')
    stop_parser.add_argument('service', help='Имя сервиса')
    stop_parser.add_argument('--force', action='store_true', help='Принудительная остановка')
    stop_parser.set_defaults(func=cmd_stop)
    
    args = parser.parse_args()
    
    if hasattr(args, 'func'):
        args.func(args)
    else:
        parser.print_help()

if __name__ == "__main__":
    main()

Использование:

python service_manager.py status nginx
python service_manager.py start nginx --wait
python service_manager.py stop nginx --force

Сравнение подходов

Критерий sys.argv argparse click
Простота использования Очень просто Средне Просто
Встроенность Да Да Нет (pip install)
Автогенерация help Нет Да Да
Валидация типов Ручная Да Да
Подкоманды Сложно Да Отлично
Для больших CLI Не подходит Подходит Отлично

Интеграция с системными утилитами

Твои скрипты должны хорошо играть с системными утилитами. Вот несколько важных моментов:

Правильные коды возврата

import sys

def main():
    try:
        # Твоя логика
        if success:
            sys.exit(0)  # Всё хорошо
        else:
            sys.exit(1)  # Общая ошибка
    except KeyboardInterrupt:
        sys.exit(130)  # Прерывание пользователем
    except Exception as e:
        print(f"Критическая ошибка: {e}", file=sys.stderr)
        sys.exit(2)  # Критическая ошибка

Обработка сигналов

Для долгоживущих скриптов важно корректно обрабатывать сигналы:

import signal
import sys
import time

def signal_handler(signum, frame):
    print(f"\nПолучен сигнал {signum}, завершаем работу...")
    # Здесь можно добавить cleanup
    sys.exit(0)

def main():
    # Устанавливаем обработчики сигналов
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)
    
    parser = argparse.ArgumentParser()
    parser.add_argument('--interval', type=int, default=60, help='Интервал проверки в секундах')
    args = parser.parse_args()
    
    print(f"Запущен мониторинг с интервалом {args.interval} секунд")
    print("Нажмите Ctrl+C для остановки")
    
    while True:
        # Твоя логика мониторинга
        print("Проверяем состояние...")
        time.sleep(args.interval)

if __name__ == "__main__":
    main()

Работа с конфигурационными файлами

Часто параметры удобнее хранить в конфиге, но позволять переопределять их через командную строку:

import argparse
import configparser
import os

def load_config(config_file):
    """Загрузка конфигурации из файла"""
    config = configparser.ConfigParser()
    config.read(config_file)
    return config

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--config', default='/etc/myapp/config.ini',
                       help='Путь к конфигурационному файлу')
    parser.add_argument('--host', help='Хост (переопределяет конфиг)')
    parser.add_argument('--port', type=int, help='Порт (переопределяет конфиг)')
    parser.add_argument('--debug', action='store_true', help='Режим отладки')
    
    args = parser.parse_args()
    
    # Загружаем конфиг если файл существует
    config = {}
    if os.path.exists(args.config):
        cfg = load_config(args.config)
        config = dict(cfg['DEFAULT']) if 'DEFAULT' in cfg else {}
    
    # Параметры командной строки имеют приоритет
    host = args.host or config.get('host', 'localhost')
    port = args.port or int(config.get('port', 8080))
    debug = args.debug or config.get('debug', '').lower() == 'true'
    
    print(f"Подключаемся к {host}:{port}, debug={debug}")

if __name__ == "__main__":
    main()

Логирование и отладка

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

import argparse
import logging
import sys

def setup_logging(level, log_file=None):
    """Настройка логирования"""
    log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    
    if log_file:
        logging.basicConfig(
            level=getattr(logging, level.upper()),
            format=log_format,
            handlers=[
                logging.FileHandler(log_file),
                logging.StreamHandler(sys.stdout)
            ]
        )
    else:
        logging.basicConfig(
            level=getattr(logging, level.upper()),
            format=log_format,
            stream=sys.stdout
        )

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--log-level', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'], 
                       default='INFO', help='Уровень логирования')
    parser.add_argument('--log-file', help='Файл для записи логов')
    parser.add_argument('--quiet', action='store_true', help='Минимальный вывод')
    
    args = parser.parse_args()
    
    if args.quiet:
        log_level = 'ERROR'
    else:
        log_level = args.log_level
    
    setup_logging(log_level, args.log_file)
    logger = logging.getLogger(__name__)
    
    logger.info("Скрипт запущен")
    logger.debug("Отладочная информация")
    
    # Твоя логика
    
    logger.info("Скрипт завершён")

if __name__ == "__main__":
    main()

Интересные факты и нестандартные применения

Несколько трюков, которые могут пригодиться:

Динамическое создание аргументов

Можно создавать аргументы на лету, например, для работы с плагинами:

import argparse
import importlib

def load_plugins():
    """Загрузка плагинов и их аргументов"""
    plugins = {}
    plugin_names = ['plugin1', 'plugin2']  # Или сканирование директории
    
    for name in plugin_names:
        try:
            module = importlib.import_module(f'plugins.{name}')
            plugins[name] = module
        except ImportError:
            pass
    
    return plugins

def main():
    parser = argparse.ArgumentParser()
    
    # Базовые аргументы
    parser.add_argument('--plugin', choices=['plugin1', 'plugin2'], required=True)
    
    # Загружаем плагины и добавляем их аргументы
    plugins = load_plugins()
    for name, module in plugins.items():
        if hasattr(module, 'add_arguments'):
            group = parser.add_argument_group(f'Plugin: {name}')
            module.add_arguments(group)
    
    args = parser.parse_args()
    
    # Запускаем выбранный плагин
    if args.plugin in plugins:
        plugins[args.plugin].run(args)

if __name__ == "__main__":
    main()

Автодополнение для bash

Модуль argcomplete добавляет автодополнение для твоих скриптов:

# pip install argcomplete
import argparse
import argcomplete

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--host', help='Hostname')
    parser.add_argument('--service', choices=['nginx', 'apache', 'mysql'])
    
    # Включаем автодополнение
    argcomplete.autocomplete(parser)
    
    args = parser.parse_args()
    # Твоя логика

if __name__ == "__main__":
    main()

Активация автодополнения:

# Для конкретного скрипта
eval "$(register-python-argcomplete my_script.py)"

# Глобально для всех Python-скриптов
activate-global-python-argcomplete

Тестирование CLI-скриптов

Важно тестировать обработку аргументов. Вот простой пример:

import unittest
import sys
from io import StringIO
from unittest.mock import patch
import argparse

class TestCLI(unittest.TestCase):
    def setUp(self):
        self.parser = argparse.ArgumentParser()
        self.parser.add_argument('--host', default='localhost')
        self.parser.add_argument('--port', type=int, default=80)
    
    def test_default_values(self):
        args = self.parser.parse_args([])
        self.assertEqual(args.host, 'localhost')
        self.assertEqual(args.port, 80)
    
    def test_custom_values(self):
        args = self.parser.parse_args(['--host', 'example.com', '--port', '443'])
        self.assertEqual(args.host, 'example.com')
        self.assertEqual(args.port, 443)
    
    def test_invalid_port(self):
        with self.assertRaises(SystemExit):
            self.parser.parse_args(['--port', 'invalid'])
    
    @patch('sys.argv', ['script.py', '--host', 'test.com'])
    def test_sys_argv_mocking(self):
        args = self.parser.parse_args()
        self.assertEqual(args.host, 'test.com')

if __name__ == '__main__':
    unittest.main()

Автоматизация и интеграция

Правильно написанные CLI-скрипты отлично интегрируются с системами автоматизации:

Использование в cron

# Добавляем в crontab
# Проверка дискового пространства каждые 30 минут
*/30 * * * * /usr/bin/python3 /opt/scripts/disk_monitor.py / /var -t 2.0 --exit-on-warning

# Еженедельный бэкап
0 2 * * 0 /usr/bin/python3 /opt/scripts/backup.py /home /backup --compression gzip

Интеграция с Ansible

- name: Проверка дискового пространства
  command: python3 /opt/scripts/disk_monitor.py {{ ansible_mounts | map(attribute='mount') | join(' ') }} -t 5.0 --exit-on-warning
  register: disk_check
  failed_when: disk_check.rc != 0

Использование в Docker

# Dockerfile
FROM python:3.9-slim
COPY my_script.py /app/
WORKDIR /app
ENTRYPOINT ["python3", "my_script.py"]

# Запуск с параметрами
docker run my-script --host example.com --port 443 --verbose

Безопасность и валидация

Не забывай о безопасности при обработке пользовательского ввода:

import argparse
import re
import os
from pathlib import Path

def validate_hostname(hostname):
    """Валидация hostname"""
    if not re.match(r'^[a-zA-Z0-9.-]+$', hostname):
        raise argparse.ArgumentTypeError(f"Недопустимый hostname: {hostname}")
    return hostname

def validate_path(path):
    """Валидация пути"""
    path_obj = Path(path).resolve()
    
    # Проверяем, что путь не выходит за пределы разрешённых директорий
    allowed_dirs = ['/var/log', '/opt/data', '/tmp']
    if not any(str(path_obj).startswith(allowed) for allowed in allowed_dirs):
        raise argparse.ArgumentTypeError(f"Путь {path} не разрешён")
    
    return str(path_obj)

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--host', type=validate_hostname, required=True)
    parser.add_argument('--log-path', type=validate_path)
    parser.add_argument('--port', type=int, choices=range(1, 65536), metavar='1-65535')
    
    args = parser.parse_args()
    
    # Теперь можно безопасно использовать валидированные данные
    print(f"Подключаемся к {args.host}:{args.port}")

if __name__ == "__main__":
    main()

Полезные ресурсы

Выводы и рекомендации

Правильная обработка аргументов командной строки — это основа профессиональных Python-скриптов. Вот мои рекомендации:

  • Для простых скриптов (1-2 параметра) используй sys.argv
  • Для серьёзных инструментов выбирай argparse — он встроенный и мощный
  • Для сложных CLI-приложений с множеством команд бери click
  • Всегда добавляй валидацию входных данных, особенно для продакшн-скриптов
  • Используй meaningful exit codes для интеграции с системными утилитами
  • Добавляй подробный help — будущий ты скажет спасибо
  • Комбинируй с конфигурационными файлами для гибкости

Хорошо спроектированный CLI — это не только удобство использования, но и основа для автоматизации. Такие скрипты легко интегрируются с cron, Ansible, Docker и другими инструментами инфраструктуры.

Начни с простого argparse для своих скриптов мониторинга и бэкапа, а когда освоишься — переходи к более сложным решениям с подкомандами и плагинами. Главное — делать скрипты предсказуемыми и удобными для использования в автоматизации.


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

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

Leave a reply

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