Обработка аргументов командной строки в Python

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

При запуске любой программы существует возможность передавать ей параметры, называемые аргументами командной строки (далее - АКС). Несмотря на название, АКС можно передавать и графическим приложениям. Формат и содержание АКС могут быть произвольными и определяются кодом самой программы. Каждый язык программирования предлагает средства для получения и обработки АКС (чаще всего - в виде массива или списка строк).

Под задачей разбора АКС понимается процесс извлечения из АКС каких-либо значений и сохранение их в отдельных переменных программы. Это делается для того, чтобы в дальнейшем разработчику было удобнее получать доступ к значениям конкретных АКС.

Если программа имеет большое количество возможных АКС, задача их разбора усложняется. Написание обрабатывающего кода для каждого аргумента оказывается монотонной работой, в которой легко допустить ошибку. Для автоматизации разбора АКС многие ЯП предоставляют соответствующие библиотеки. Идея, заложенная в них, заключается в следующем:

К библиотекам разбора АКС относятся: getopt для C, boost::program_options для C++, CommandLineParser для C#, optparse-applicative для Haskell и argparse для Python, с которой мы познакомимся в этой работе.

Структура аргументов командной строки

В результате появления библиотек для разбора АКС, и несмотря на отсутствие стандартизации в этой области, сложились негласные правила форматирования АКС. Согласно этим правилам, АКС подразделяются на следующие группы:

  1. Флаги (англ. flags).
  2. Опции (англ. options).
  3. Позиционные аргументы (англ. positional arguments).
  4. Команды (англ. commands или subcommands).

Флаги и опции предваряются одним или несколькими специальными символами, которые и отличают их от позиционных аргументов и команд. Наиболее распространённым символов является '-', а в ОС Windows традиционно используется '/'. Спецсимвол пишется один раз перед однобуквенными флагами и опциями и дважды перед многобуквенными. Опции отличаются от флагов тем, что они требуют значение в качестве параметра, в то время как флаги параметров не имеют. Некоторые флаги и опции могут одновременно иметь краткий однобуквенный и длинный многобуквенный варианты. Рассмотрим применение флагов и опций на примере консольных приложений Unix-подобных ОС:

# выводит список файлов в текущей директории в виде списка
ls -l

# выводит количество слов и строк в файле input.txt
wc -w -l input.txt

# находит все вхождения слова "what" во всех файлах внутри директории "where"
grep -r --color what where

# выводит первые 5 строк из файла input.txt
head -n 5 input.txt

АКС -l, -w, -r и --color в примере выше являются опциями, т.к. не принимают никаких значений в качестве параметров; АКС -n является опцией, т.к. он принимает количество строк, которые необходимо вывести; АКС input.txt, what и where - позиционными аргументами, т.к. не начинаются со спецсимвола.

Некоторые библиотеки позволяют объединять несколько однобуквенных флагов или опций, идущих подряд, в один АКС без потери смысла. Например, команду wc из примера выше можно записать так:

wc -wl input.txt

Параметры однобуквенных опций можно так же объединять с самой опцией:

head -n5 input.txt

Многобуквенные опции отделяются от параметра пробелом или символом =:

# распаковывает архив archive.tar
tar -xv --file=archive.tar

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

# удаляет file1.txt и file2.txt
rm file1.txt file2.txt

# посылает эхо-запрос на сайт onlyfans.com
ping onlyfans.com

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

# копирует file1.txt в file2.txt

cp file1.txt file2.txt

# наоборот, копирует file2.txt в file1.txt

cp file2.txt file1.txt

Командами называются АКС, которые разрешают или запрещают использование других АКС, или же меняют их смысл:

# В ОС FreeBSD выводит аннотацию для установленного пакета
# "info" - команда, которая выводит информацию о заданном пакете
# "-A" - флаг, который при команде info выводит аннотацию
pkg info -A firefox

# Устанавливает пакет и помечает его как установленный автоматически
# "install" - команда для установки пакетов
# "-A" - флаг, который при команде install помечает его автоматически установленным
pkg install -A firefox

Библиотека argparse

В этом разделе изложим краткое руководство по использованию модуля argparse. Подробная информация о нём находится в официальной документации Python.

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

import argparse

Парсер создаётся вызовом конструктора ArgumentParser(), а его запуск производится с помощью метода parse_args():

parser = argparse.ArgumentParser()

parser.parse_args()

Несмотря на то, что мы ещё не объявили ни одного АКС, argparse автоматически создаёт флаг -h,--help, который выводит справку по использованию программы. Все остальные АКС должны объявляться разработчиком перед вызовом parse_args().

Перечислим некоторые именованные аргументы конструктора ArgumentParser():

Перепишем пример выше с использованием этих аргументов:

parser = argparse.ArgumentParser(prog="myprog", description="My awesome program",
                                 epilog="All rights reserved", prefix_chars="-+",
                                 allow_abbrev=False, exit_on_error=False)

parser.parse_args()

Добавление АКС осуществляется с помощью метода add_argument():

parser.add_argument("filename")

В примере выше объявляется позиционный аргумент с именем filename. Запустив программу с флагом -h можно увидеть, что этот аргумент появился в справке. Рассмотрим подробно параметры метода:

Рассмотрим подробный пример, использующий описанные выше параметры:

# опция -f с синонимом --foo
parser.add_argument("-f", "--foo")

# флаг -g, сохраняющий значение 42, если он был передан
parser.add_argument("-g", action="store_const", const=42)

# флаги --yes и --no, сохраняющие, если переданы, True и False, соответственно.
parser.add_argument("--yes", action="store_true")
parser.add_argument("--no", action="store_false")

# опция -h, который можно указывать несколько раз, значения которого будут собраны в список
parser.add_argument("-h", action="append")

# флаги --with-pasta и --with-pizza, которые, если указаны, собирают в список
# под именем order строковые константы "pasta" и "pizza", соответственно
parser.add_argument("--with-pasta", action="append_const", dest="order", const="pasta")
parser.add_argument("--with-pizza", action="append_const", dest="order", const="pizza")

# флаг -v, сохраняющий количество вхождений в командную строку
# например, "-v -v -v" будет сохранять 3
parser.add_argument("-v", action="count", default=0)

# опция --items, которую можно указывать несколько раз, и которая может иметь
# одно или более значений
# например, --items ball --items pen boots сохранит ["ball", "pen", "boots"]
parser.add_argument("--items", action="extend", nargs="+")

# опция --between, принимающая строго два параметра
parser.add_argument("--between", nargs=2)

# позиционный параметр infile, который если не указан, использует значение по
# умолчанию "input.txt"
parser.add_argument("infile", nargs="?", default="input.txt")

# позиционный параметр direction, который может принимать
# только значения "up", "down", "left" и "right"
parser.add_argument("direction", choices=["up", "down", "left", "right"])

В результате успешного завершения метода parse_args() возвращается объект, содержащий значения разобранных АКС в качестве своих полей. Имена полей определяются параметром dest при вызове add_argument():

parser = argparse.ArgumentParser()

parser.add_argument("filename")

args = parser.parse_args()

print(args.filename)

Формат CSV

Для выполнения данной работы необходимо также познакомиться с форматом CSV.

CSV (англ. Comma Separated Values, значения разделённые запятыми) - текстовый формат представления простых табличных данных. Для создания CSV-файла достаточно обычного текстового редактора, однако табличные процессоры вроде LibreOffice Calc и Microsoft Excel так же имеют возможность экспортировать данные в CSV. Пример содержимого простейшего CSV-файла приведён ниже:

ФИО,Стаж,Зарплата
Дегтярёв Данил Игоревич,2,7000
Попов Глеб Александрович,7,300000/сек

Несмотря на название, CSV-файлы иногда разделяются не символами ,, а какими-либо другими. Microsoft Excel разделяет ячейки с помощью ; или символа табуляции, а так же имеет некоторые другие особенности форматирования.

Для чтения, обработки и записи CSV-файлов в языке Python имеется библиотека csv. Ознакомимся с её программным интерфейсом.

Функция reader() возвращает объект-читатель, который позволяет считывать содержимое CSV-файла построчно. Идея этого объекта во многом напоминает идею, стоящую за потоками ввода/вывода (StreamReader и FileStream в C#). В качестве первого её параметра передаётся любой итерируемый объект, из которого будет черпаться информация. Это означает, что объект-читатель CSV может читать данные не только из файлов, но и, например, из переменных-списков. Именованные аргументы функции reader() включают:

Функция writer() принимает тот же набор аргументов, что и reader(), и возвращает объект-писатель. Этот объект аналогичен StreamWriter в C# и позволяет построчно записывать данные в формате CSV.

Объект-читатель может производить чтение построчно путем организации цикла for по этому объекту. Чтобы прочитать файл целиком, объект-читатель нужно сконвертировать в список.

Объект-писатель имеет метод writerow(), принимающий список ячеек и записывающий очередную строку в CSV-файл, а так же метод writerows(), принимающий список списков ячеек.

Важно иметь ввиду, что при вызове reader() или writer() над файлами, необходимо обязательно указывать параметр newline='' при их открытии. Приведём пример, демонстрирующий применение описанных функций:

import csv

infile = open("input.csv", newline='')
reader = csv.reader(infile, delimiter='\t', quotechar='|', skipinitialspace=True)

for line in reader:
  for cell in line:
    print("Ячейка содержит " + cell)

outfile = open("output.csv", "w", newline='')
writer = csv.writer(outfile)

l1 = ["a", "b", "c"]
l2 = ["x", "y", "z"]

writer.writerow(l1)
writer.writerow(l2)

Более подробную информацию о модуле csv можно найти в официальной документации.

Контрольные вопросы

  1. Виды аргументов командной строки и их отличия.
  2. Что такое парсер АКС и какую задачу он решает?
  3. Что означают символы {} и [] в справке по использованию программы?
  4. Что такое экранирование символов?

Задание на лабораторную работу

  1. Написать программу на Python, обрабатывающую аргументы командной строки в соответствии с вариантом. Программа должна прочитывать матрицу из CSV-файла и производить над ней операции, в зависимости от указанных аргументов командной строки. Изменённая матрица должна выводиться в выходной CSV-файл, путь до которого так же указывается в аргументах командной строки. В программе должна присутствовать обработка ошибочного ввода.
  2. Запустить написанную программу и вывести справку по её использованию, а затем убедиться в ее правильном функционировании при введённых входных данных.

Варианты заданий

Вариант 1

Вариант 2

Вариант 3

Вариант 4

Вариант 5

Вариант 6

Вариант 7

Программа зануляет элементы матрицы вокруг элемента, указанного через -x и -y. Если квадрат, элементы которого зануляются, выходит за пределы матрицы (напр. -x 0 -y 0), то программа выдаёт ошибку.

Вариант 8

Вариант 9

Программа суммирует строки от r1 до r2 поэлементно, а получившуюся строку подставляет в матрицу вместо строк, использовавшихся для суммирования. Если -r1 не указан, то программа использует строки от первой до r2. Если -r2 не указан, то программа использует строки от r1 до последней.

Вариант 10

Вариант 11

Вариант 12

Программа вырезает из матрицы прямоугольник элементов, левый верхний угол которого имеет индекс (x1,y1), а правый нижний - (x2,y2). В выходной файл записывается вырезанная подматрица. Программа не должна допускать вырезания единственного элемента, если только не указан необязательный параметр -o.

Вариант 13

Вариант 14

Вариант 15