- Подписка на печатную версию:
- Подписка на электронную версию:
- Подшивки старых номеров журнала (печатные версии)
LXF112:Getopt
Материал из Linuxformat.
Командная строка Анализируем аргументы приложения в соответствии со стандартом
Содержание |
Правильные аргументы
- Театр начинается с вешалки, а приложение Unix – с аргументов командной строки. Артём Коротченко подскажет, как сделать так, чтобы первое впечатление пользователя было приятным.
Рассмотрим команду cp -s file1 file2. На самом деле, все четыре слова здесь являются аргументами, а то, что мы привыкли называть ими в повседневной жизни – это опции и операнды. Например, -s – это опция, а file1 и file2 – операнды. Ещё есть понятие «аргумент опции»:
/usr/bin/openssl des -in backup.tar.gz -out backup.sec
В данном примере backup.tar.gz и backup.sec являются аргументами опций -in и -out. Впрочем, мы не будем заострять внимание на этих тонкостях и используем привычную для большинства людей терминологию: аргументы – это слова, следующие за именем команды.
Подавляющее большинство ПО для Linux и Unix использует для своей работы аргументы командной строки. Даже программы с графическим интерфейсом, которым, казалось бы, и вовсе не нужна консоль, интерпретируют десятки входных параметров. Это может быть задание начальных настроек, включение отладочного режима или просто вывод версии программы без её запуска. В общем, аргументы используются повсеместно – так уж исторически сложилось в Unix-системах. Ну, а раз без аргументов никуда, надо уметь с ними работать. К счастью, библиотеки Unix и здесь придут вам на помощь... но давайте обо всем по порядку.
Вначале был argv...
Основная функция любой C/C++ программы описывается следующим образом:
int main (int argc, char *argv[] { . . . };
Или так – это вопрос вкуса:
int main (int argc, char **argv) { . . . };
Массив argv содержит аргументы командной строки, argc указывает, сколько их всего было передано, причем имя программы также включается сюда: оно доступно как argv[0]. Поэтому, чтобы работать с командной строкой, самым простым программам вполне хватало параметров функции main().
# ./myinternetprogram
Использование: ./myinternetprogram <hostname> В этом случае весь процесс обработки аргументов сводится к проверке их количества.
int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Использование: %s <hostname>\n", argv[0]); return 1; } ... }
Понятно, что когда весь синтаксис для запуска какой-то утилиты исчерпывается одним или парой аргументов, не нужно изобретать никаких новых библиотек.
Пришествие getopt()
Трудности в мире Unix начались тогда, когда аргументов стало больше трёх. Конечно, дело тут даже не в количестве, а в том, что потребовалась большая гибкость при их анализе.
# cp --help Использование: cp [КЛЮЧ]... [-T] ИСТОЧНИК НАЗНАЧЕНИЕ или: cp [КЛЮЧ]... ИСТОЧНИК... КАТАЛОГ или: cp [КЛЮЧ]... -t КАТАЛОГ ИСТОЧНИК...
Количество аргументов может меняться. Одни из них обязательные, другие – нет. Одним нужен дополнительный параметр (--backup[=CONTROL]), другим – не нужен (-l, -r, -s). Представляете, как реализовать всю эту логику? Код усложнялся, рос всё больше и больше. При этом каждая программа использовала свои методики для разбора командной строки.
Не говорю уже об эстетической стороне! У всех различный синтаксис. Кому-то нужен один дефис перед аргументами, кому-то два, кому-то вообще не нужен. Кто-то обрамлял аргументы двойными кавычками, кто-то одинарными, а кто-то не выделял вообще. Никакого единообразия. Так проблема постепенно перешла с плеч программистов на плечи пользователей (впрочем, в те далекие времена это было почти одно и то же).
В общем, нужно было что-то делать. И тогда группой поддержки Unix был написан ряд POSIX-соглашений, который должен был предоставить универсальное решение. Согласно им, все опции должны начинаться с дефиса и могут объединяться под этим символом (./test -x -a -b – то же самое, что и ./test -xab). Опции не могут состоять из цифр. Порядок аргументов не должен играть роли, иначе это необходимо документировать. И так далее; желающие ознакомиться с полным перечнем соглашений могут обратиться по ссылке: http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html
Итак, стандарт POSIX гарантировал пользователям Linux и Unix удобную работу с программами, которые его придерживаются. Но как быть с программистами? Ответ кроется в getopt(), функции, воплотившей требования POSIX в жизнь.
Немного практики
Начнем с приятного: пользоваться getopt() очень просто! Сложно только ее понять. Собственно, синтаксис выглядит так:
#include <unistd.h> int getopt(int argc, char * const argv[], const char *optstring); extern char *optarg; extern int optind, opterr, optopt;
Функция вызывается в цикле, последовательно перебирая аргументы командной строки. На каждой итерации возвращается соответствующий аргумент. Удобно использовать конструкцию switch для того, чтобы смотреть, какая опция анализируется в настоящий момент, и обрабатывать ее подходящим образом. Как только все аргументы закончатся, функция вернёт -1. Возможны и другие возвращаемые значения – см. пример ниже.
С выходом getopt() мы разобрались – а что на входе? argc и argv, понятно, получаются от main(). Optstring – это опции, которые ожидаем получить. Двоеточие после опции означает, что у неё должен быть аргумент:
int rs; opterr = 0; while ((rs = getopt(argc,argv,":ab:c")) != -1) { switch (rs) { case "a": break; case "b": fprintf(stderr, "%s - аргумент опции b", optarg); break; case "c": break; case ":": fprintf(stderr, "%s: опции '-%c' требуется аргумент\n", argv[0], optopt); exit(1); case "?": default: fprintf(stderr, "%s: опция '-%c' неверна\n", argv[0], optopt); exit(1); }; }
Даже самые простые программы будут запускаться с неверными опциями или недостающими аргументами. Это очевидно – людям свойственно ошибаться. В этом случае функция getopt() выводит сообщение об ошибке, но мы можем препятствовать такому поведению (например, чтобы уведомить пользователя о некорректной опции каким-то другим образом). Этого можно достичь двумя способами: либо указать первым символом optstring двоеточие, либо приравнять переменную opterr нулю.
Приведённый выше пример использует оба сразу. Как видно, getopt() будет возвращать :, если для опции не указан аргумент, и ?, если введена вообще неверная опция. Учтите: в случае, когда первым символом optstring не является двоеточие, getopt() возвращает ? для обоих видов ошибок.
Теперь настало время сказать о переменных:
- optarg – аргумент опции. В нашем случае он присутствует только для -b.
- optind – индекс просматриваемого в данный момент элемента массива argv.
- opterr – как уже было сказано, нужно приравнять нулю, если мы хотим запретить getopt выводить свои сообщения об ошибках.
- optopt – имя опции (если getopt вернула : или ?, а нам её всё равно нужно узнать).
Длинная история
Программы GNU используют длинные опции, которые начинаются с двух дефисов и содержат полные английские слова. Их можно сочетать с короткими опциями (например, -R и --recursive, -h и --help), а можно и не сочетать, т.е. использовать в одиночку. Чтобы анализировать длинные опции, существует функция getopt_long() (вообще, я бы советовал только ею и ограничиться). Наряду с getopt_long() существует функция getopt_long_only(): она не работает с короткими опциями, хотя в остальном аналогична getopt_long().
#include <getopt.h> int getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex); int getopt_long_only(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex);
longindex связан с диагностикой ошибок и в общем случае не нужен – передаём NULL. Первые три параметра аналогичны тем, что мы указывали для getopt(). Вопросы может вызвать только структура longopts. Синтаксис у неё следующий:
struct option { const char *name; int has_arg; int *flag; int val; };
- name – имя длинной опции.
- has_arg – нужен ли ей аргумент. В качестве значения можно указать no_argument (не нужен), required_argument (нужен) или optional_argument (аргумент может быть, а может и не быть). Все три значения являются макроподстановками из getopt.h и равны 0, 1 и 2, соответственно.
- flag – либо NULL, либо указатель на переменную. В первом случае getopt_long() возвращает значение поля val этой структуры. Во втором getopt_long() возвращает 0, а переменная, на которую указывает flag, заполняется значением val.
- val – имя короткой опции, которая соответствует длинной опции, если flag равен NULL. Для случая, когда flag указывает на переменную, это значение, которое в него запишется, если программа встретит такой аргумент.
Согласен, все это звучит несколько путано, поэтому проиллюстрируем вышесказанное примером:
struct option longopts[] = { { "help", no_argument, &do_help, 1 }, { "version", no_argument, &do_version, 1 }, { "verbose", no_argument, NULL, 'v' } };
Если пользователь ввёл опцию --help, do_help будет равен 1. Если не ввёл – значение do_help при вызове getopt_long() не изменится. Аналогично происходит и с --version.
Вообще, возможность использовать флаговые переменные таким образом хороша тем, что не нужно самостоятельно указывать в цикле getopt_long(), что делать после выхода из него. Зато нельзя использовать короткие опции. Для --verbose придётся установить флаговую переменную в цикле вручную, зато будет работать опция -v.
Все вместе
Чтобы свести полученные знания воедино, давайте напишем Unix-команду yes, совершенно безумную утилиту, цель которой – выводить принятый аргумент до тех пор, пока её не убьют (полный текст можно найти на диске).
int main(int argc, char *argv[]) { ... struct option longopts[] = { { "help", no_argument, &do_help, 1 }, { "version", no_argument, &do_version, 1 }, }; myname = argv[0]; opterr = 0; while ((rs = getopt_long(argc, argv, "-", longopts, NULL)) != -1) { switch (rs) { case 0: break; case 1: txt = xrealloc(txt, strlen(optarg) + strlen(txt) + 2); strcat(txt, optarg); strcat(txt, " "); break; case ':': fprintf(stderr, "%s: опции '-%c' требуется аргумент\n", argv[0], optopt); return 1; case '?': default: fprintf(stderr, "%s: опция '-%c' неверна\n", argv[0], optopt); return 1; } } if (do_help) { ... } if (do_version) { ... } if (argc < 2) strcpy(txt, "y"); while (1) printf("%s\n", txt); return 0; }
Скомпилируйте программу командой
gcc fakeyes.c -o fakeyes
и начинайте тестирование. Кстати, наш yes ничем не уступает оригиналу.
./fakeyes --help ./fakeyes --version ./fakeyes -c ./fakeyes ./fakeyes хорошо отлично
LXF
Не только для C
Функции getopt() и getopt_long() были разработаны для С, но понятно, что другие языки, такие как Java, Perl, Python, PHP, не могли обойти своим вниманием столь мощные возможности анализа аргументов. Существует реализация и для оболочки: в пакет util-linux, который присутствует в большинстве дистрибутивов, входит утилита getopt. С её помощью можно разбирать аргументы сценариев. Например:
#!/bin/bash rs=`getopt ab:c $*` set -- $rs for i do echo "$i" done
Проверим
./test -x -a -b getopt: invalid option -- x getopt: option requires an argument -- b -a --
В качестве альтернативы можно использовать getopts.