- Home »

Аргументы командной строки в 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()
Полезные ресурсы
- Официальная документация argparse
- Click - Python composable command line interface toolkit
- argcomplete - Bash tab completion for argparse
- Argparse Tutorial
Выводы и рекомендации
Правильная обработка аргументов командной строки — это основа профессиональных Python-скриптов. Вот мои рекомендации:
- Для простых скриптов (1-2 параметра) используй sys.argv
- Для серьёзных инструментов выбирай argparse — он встроенный и мощный
- Для сложных CLI-приложений с множеством команд бери click
- Всегда добавляй валидацию входных данных, особенно для продакшн-скриптов
- Используй meaningful exit codes для интеграции с системными утилитами
- Добавляй подробный help — будущий ты скажет спасибо
- Комбинируй с конфигурационными файлами для гибкости
Хорошо спроектированный CLI — это не только удобство использования, но и основа для автоматизации. Такие скрипты легко интегрируются с cron, Ansible, Docker и другими инструментами инфраструктуры.
Начни с простого argparse для своих скриптов мониторинга и бэкапа, а когда освоишься — переходи к более сложным решениям с подкомандами и плагинами. Главное — делать скрипты предсказуемыми и удобными для использования в автоматизации.
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.