Журнал LinuxFormat - перейти на главную

LXF112:Getopt

Материал из Linuxformat
Перейти к: навигация, поиск

Командная строка Анализируем аргументы приложения в соответствии со стандартом

Содержание

Правильные аргументы

Театр начинается с вешалки, а приложение Unix – с аргументов командной строки. Артём Коротченко подскажет, как сделать так, чтобы первое впечатление пользователя было приятным.


Подавляющее большинство ПО для 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.

Персональные инструменты
купить
подписаться
Яндекс.Метрика