В 15 выпуске Perl-журнала PragmaticPerl опубликована моя статья “Простые способы сделать консольную утилиту удобнее”: ссылка. Здесь находится дополняемая и улучшаемая версия этой статьи.
Качественная командная строка – отличнейшее окружение для работы. История, автокомплит, конвейеры, перенаправления, широчайший набор готовых программ, возможность скопировать-и-вставить даже самую сложную команду – все это делает CLI мощным и удобным инструментом. Ничего удивительного, что разработчики охотно пишут собственные консольные программы и скрипты: автоматизация сборки и деплоя, разворачивание тестовой БД, статистика, мониторинг, хитроумный поиск – поводов не счесть.
Однако «консольная утилита» не означает автоматически «удобная в использовании утилита». Неудачное имя, неполное или отсутствующее описание, большое количество позиционных параметров, запутанное именование переключателей – командную строку можно наполнить когнитивным сопротивлением1 похлеще самого запутанного графического интерфейса.
В этой статье собраны несложные советы, следование которым поможет сделать свои консольные скрипты более удобными в работе.
Оговорки: советы довольно простые, и если они покажутся вам само собой разумеющимися – отлично. К сожалению, даже о таких простых вещах временами забывают. Далее, я предполагаю, что и вы, и ваши пользователи живете в unix-подобном окружении: полноценный шелл и полноценный набор стандартных программ. Кроме того, я использую слова «программа», «скрипт» и «утилита» как взаимозаменяемые синонимы. И наконец, в качестве примеров в статье приведены фрагменты кода на Perl, однако все советы в равной мере относятся к скриптам и программам на любом языке программирования, а технические приемы легко на любой язык транслируются.
Лучший скрипт – тот, который не надо писать. Может быть, задача решается
простой комбинацией уже существующих программ? Утилиты make
, sort
,
find
, grep
, lsof
, netstat
, strace
и т.д. не только хороши сами
по себе, но и отлично склеиваются через конвейеры («пайпы»).
А может быть, задача решается коротким perl-однострочником, который проще написать заново2, чем вспоминать название готового скрипта? Кстати, perl-однострочники тоже прекрасно встраиваются в конвейеры.
Какое оно – хорошее имя для хорошей программы? Короткое или длинное? Абстрактное или описывающее поведение? Однословное или составное?
Вот несколько простых правил:
ls
и гораздо более редкую
apt-get install
;
make
и донельзя
конкретное ps2pdf
;
make-release
, и rc1
, но не стоит называть
его test-mainline
, launch
или new-version
.
Если параметры скрипта становятся сложнее, чем простой список файлов
(как у rm
) или пары «что–куда» (как в cp
), «что–где» (как в
grep
) – скрипту нужны именованные параметры командной строки.
Getopt::Long входит в стандартную поставку perl с 1994 года, и позволяет легко разбирать самые разнообразные опции:
--cache
, --no-cache
),
-q
и --quiet
, -h
и --help
)
perl -lane
, ls -la
).
В общем, изучить документацию на Getopt::Long
и попрактиковаться в его
применении – стоящее дело.
Мне кажется, что практически для каждой опции стоит иметь и
однобуквенный, и многобуквенный варианты (как -q
и --quiet
).
Однобуквенные ключи удобны при интерактивной работе, так как их быстрее
набирать, а многобуквенные – в мейкфайлах и других скриптах, так как
более понятны при чтении.
Хотите, чтобы пользователи быстрее запомнили параметры вашей утилиты – называйте привычные действия привычными именами:
-h
, --help
– помощь,
-V
, --version
– вывод версии программы,
-q
, --quiet
, --silent
– режим с менее подробным выводом,
-v
, --verbose
– режим с более подробным выводом (интересный
пример находим в ssh
: -v
, -vv
, -vvv
дают все более и более
подробное логирование),
-n
, --dry-run
– пробный запуск без выполнения пишущих действий,
или -n
– количество элементов, которые следует обработать,
-o
, --output
– файл для записи результата,
-f
, --file
– файл с данными для обработки,
-r
, --reverse
– обработка в обратном порядке,
-j
, --jobs
, --parallel
– во сколько процессов распараллеливать
обработку.
Антипримеры можно часто наблюдать в Windows-версиях популярных
Unix-программ. Например, ping
: бесконечная отправка пакетов включается
ключом -t
вместо умолчального поведения, количество пакетов
регулируется ключом -n
вместо -c
, размер пакета -l
вместо -s
,
TTL -i
вместо -t
и т.п. Или tracert
в сравнении с traceroute
:
максимальное число прыжков -d
и -m
соответственно, не резолвить
адреса в имена -d
и -n
. Такой разнобой в именовании очень неудобен,
особенно если приходится работать попеременно то с одним, то с другим
вариантом программы.
Если переданные скрипту параметры не проходят разбор и валидацию, надо корректно сообщить об этом пользователю:
GetOptions(...) or die "can't parse command line arguments, stop\n";
die "You must give at least one search pattern" unless
exists $OPT{search_pattern};
Важно, чтобы сообщение об ошибке было коротким и ясно говорило, что именно не так с параметрами. Недопустимо в ответ на неправильные параметры выводить полную справку – это никак не поможет вашему потребителю понять, что происходит.
Посмотрим, как ведут себя популярные программы:
> git up
git: 'up' is not a git command. See 'git --help'.
Did you mean one of these?
pull
push
> grep --li
grep: option '--li' is ambiguous; possibilities: '--line-buffered'
'--line-regexp' '--line-number'
Usage: grep [OPTION]... PATTERN [FILE]...
Try `grep --help' for more information.
Здесь все коротко и по существу:
Если скрипт по каким-то внутренним причинам не может продолжать работу –
пора вызывать die
(=вывести сообщение и завершиться с ненулевым
кодом).
Полезный совет: сообщение об ошибке завершать недвусмысленным ` ‘, stop.’` Это делает для пользователя очевидным, что программа остановилась именно из-за обнаруженной ошибки.
> show-releases.pl -n 10
can't connect to tracker, stop.
Скрипт обязательно должен возвращать честный код возврата: в случае успешного завершения – 0, в случае неудачи – что-нибудь другое. Правильный код выхода позволяет успешно использовать скрипт в makefile’ах, для svn-bisect и т.п.
Обратите внимание: у grep
’а есть специальный режим, когда он вообще
ничего ничего не печатает, только сигнализирует кодом выхода: нашел или
не нашел.
Кстати, Perl’овый die
автоматически обеспечивает ненулевой код
завершения.
По -h
(желательно и по --help
тоже) скрипт должен выводить справку о
себе.
Проверьте, что в справке описано:
Иногда справку пытаются выводить в stderr
. Это неправильно. Справка
должна попадать в stdout
, чтобы ее легко было обрабатывать grep
’ом,
less
’ом и т.п.
Еще можно обращать внимание на переменную окружения $PAGER
и если она
выставлена – передавать справку через пайп этой программе. Например, так
поступает git help <command>
.
Еще бывает, что вывод справки заканчивают ненулевым кодом выхода
(exit 2;
). Это неправильно. Если пользователь запрашивал справку, то
ее вывод – успешно выполненная задача и скрипт должен сообщать, что
закончился успешно (exit 0;
).
И еще одна смешная и грустная иллюстрация того, “как не надо”: ссылка (подсказана в комментариях в PragmaticPerl).
Хорошие умолчательные значения критически важны для эффективной работы с программой. Выбирать умолчания следует исходя из того, что является основной задачей программы и каким образом она будет использоваться чаще всего.
Чем чаще нужна какая-либо опция, тем проще она должна включаться, а
самое частое значение параметра должно предполагаться по умолчанию.
Идеал: программа выполняет наиболее часто требующуюся задачу вообще без
параметров (например: cal
, debuild
, gzip
, ls
, make
, passwd
,
plackup
).
И опять хороший пример подает grep
: поиск в stdin
делается по
умолчанию; поиск по списку файлов включается простым их перечислением;
рекурсивный поиск, размер контекста и нечувствительность к регистру
включаются однобуквенными опциями; экзотика типа управления буферизацией
– многобуквенными опциями.
Интересно устроено у GNU grep
управление цветной раскраской вывода: по
умолчанию при выводе на интерактивный терминал вывод раскрашен, при
выводе в файл – не раскрашен, а для ручного управления раскраской есть
многобуквенная опция --color
.
Если у вашего скрипта много возможных параметров (особенно многобуквенных), напишите и выдайте вашим пользователям функции для автокомплита (автодополнения) в популярных шеллах. Документация: для zsh, для bash.
Кстати, обратите внимание на функцию gnu_generic
в zsh
: если по
--help
ваш скрипт рассказывает о своих параметрах в достаточно
общепринятом формате, для включения автодополнения по параметрам будет
достаточно сделать
compdef _gnu_generic my-script.pl
Если вашим скриптом будут пользоваться люди в интерактивном режиме – упростите восприятие вывода, раскрасив его в разные цвета. См. например Term::ANSIColor.
Если скрипту надо спросить у пользователя пароль или иную секретную информацию, отключите отображение вводимых символов. Например, с помощью Term::ReadKey:
Таковы, по моиму опыту, простейшие способы улучшения user experience консольных программ.
Если у вас тоже есть чем поделиться на эту тему – пишите в комментариях или ответными статьями, тема того заслуживает.