Шаг 11 - Передача длинных опций в программу - getopt_long

Парсинг длинных параметров командной строки достаточно сложный процесс, поэтому бибилиотека GNU C Library имеет специальную функцию getopt_long(), которая может работать одновременно и с длинными и с короткими параметрами. Для работы только с длинными именами параметров существует функция getopt_long_only.

Для того, чтобы работать с этими функциями Вам потребуется подключить файл getopt.h. Выглядят эти функции следующим образом:

#define _GNU_SOURCE
#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);

Для работы функции getopt_long ей нужны следующие данные:

Основным отличием этих фукнций от getopt является потребность в специальном массиве. О нем и поговорим. Массив longopts состоит из записей struct option имеющих следующий вид:

struct option {
	const char *name;
	int has_arg;
	int *flag;
	int val;
};

В первом поле name задается название длинного параметра.

Поле has_arg определяет нужно ли для этого параметра значение. Для этого в getopt.h определены специальные значения:

#define no_argument            0
#define required_argument      1
#define optional_argument      2

Как видите, если значение has_arg равно 0 (no_argument), то параметр не должен иметь значение, если 1 (required_argument), то параметр должен иметь значение. Если же значение для параметра опционально, то has_arg равен 3 (optional_argument).

Поле flag задает указатель на флаг, в который помещается значение val, если найден данный параметр (сама функция при этом возвращает 0). Если указатель равен NULL, то функция возвращает значение val в качестве результата работы.

Поле var содержит значение, которое помещается в flag или возвращается в качестве результата работы функции.

Последняя запись массива longopts должна содержать нулевые значения, для того чтобы функция могла однозначно определить конец массива.

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

Давайте посмотрим пример longopt1.c:

#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>


int main (int argc, char *argv[]){

    int flag_a = 0;
    int flag_b = 0;
    int flag_c = 0;

    const char* short_options = "abc";

    const struct option long_options[] = {
        {"opta",no_argument,&flag_a,1},
        {"optb",no_argument,&flag_b,10},
        {"optc",no_argument,&flag_c,-121},
        {NULL,0,NULL,0}
    };

    while (getopt_long(argc,argv,short_options,
        long_options,NULL)!=-1);

    printf("flag_a = %d\n",flag_a);
    printf("flag_b = %d\n",flag_b);
    printf("flag_c = %d\n",flag_c);
	printf("\n");
};

После компиляции gcc longopt1.c -o longopt1 получим программу. Вот некоторые результаты работы:

dron~# ./longopt1
flag_a = 0
flag_b = 0
flag_c = 0

dron~# ./longopt1 --opta
flag_a = 1
flag_b = 0
flag_c = 0

dron~# ./longopt1 --optb --optc
flag_a = 0
flag_b = 10
flag_c = -121

dron~# ./longopt1 -a -b -c
flag_a = 0
flag_b = 0
flag_c = 0

Как видите, когда функция увидела параметры --opta, --optb или --optc она сразу же установила переменные flag_a, flag_b и flag_с значениями, которые были указаны в массиве long_options. Но посмотрите на короткие параметры -a, -b и -c. Они не были задействованы. А все от того, что в качестве результата работы функция возвращает:

Мы с вами обработку коротких параметров не предусмотрели, если Вы сейчас модифицируете код, то сможете увидеть и их:

// добавьте переменную
int rez;

// новый цикл обработки параметров
while ((rez=getopt_long(argc,argv,short_options,
	long_options,NULL))!=-1)
{
	printf("rez: %d = \'%c\'\n",rez,rez);
};

Если сейчас запустить программу, то она выдаст следующее:

dron~# ./longopt1 -abc
rez: 97 = 'a'
rez: 98 = 'b'
rez: 99 = 'c'
flag_a = 0
flag_b = 0
flag_c = 0

dron~# ./longopt1 -a -c -g
rez: 97 = 'a'
rez: 99 = 'c'
./a.out: invalid option -- g
rez: 63 = '?'
flag_a = 0
flag_b = 0
flag_c = 0

Теперь можно обрабатывать и короткие параметры, а чтобы это делать все сразу, существует как раз второй метод использования этой функции. Это когда указатели flag устанавливают в NULL, а значения val устанавливают в названия коротких параметров. При этом вся обработка результатов происходит в switch структуре. Давайте попробуем создать файл longopt2.c:

#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>


int main (int argc, char *argv[]){

	const char* short_options = "hs::f:";

	const struct option long_options[] = {
		{"help",no_argument,NULL,'h'},
		{"size",optional_argument,NULL,'s'},
		{"file",required_argument,NULL,'f'},
		{NULL,0,NULL,0}
	};

	int rez;
	int option_index;

	while ((rez=getopt_long(argc,argv,short_options,
		long_options,&option_index))!=-1){

		switch(rez){
			case 'h': {
				printf("This is demo help. Try -h or --help.\n");
				printf("option_index = %d (\"%s\",%d,%c)\n",
					option_index,
					long_options[option_index].name,
					long_options[option_index].has_arg,
					long_options[option_index].val
				);
				break;
			};
			case 's': {
				if (optarg!=NULL)
					printf("found size with value %s\n",optarg);
				else
					printf("found size without value\n");
				break;
			};
	
			case 'f': {
				printf("file = %s\n",optarg);
				break;
			};
			case '?': default: {
				printf("found unknown option\n");
				break;
			};
		};
	};
	return 0;
};

Теперь посмотрите на работу программы. Попробуем параметр --help и -h.

dron~# ./longopt2 --help
This is demo help. Try -h or --help.
option_index = 0 ("help",0,h)

dron~# ./longopt2 -h
This is demo help. Try -h or --help.
Segmentation fault

В первом случае все удачно, вывелась помощь и значение option_index. Во втором случае программа "упала" с сообщением об ошибке. Почему ? Ошибка Segmentation fault выдается когда программа пытается работать с неверными указателями. А в нашем случае мы пытаемся получить по option_index название параметра. В случае когда найден короткий параметр значение option_index не определено. Что же делать ?! А все просто. Модифицируем код чуток:

int option_index=-1; //обнулим в начале (установим признак ошибки)

while (...){
	switch(...){
	};
	option_index = -1; // снова делаем ошибку
};

При такой работе option_index можно применять для определения типа переданного параметра. Если он был длинным, то это значение будет больше нуля и равно порядковому номеру параметра в массиве. Если же -1, то это значит, что параметр короткий:

if (option_index<0)
	printf("short help option\n");
else
	printf("option_index = %d (\"%s\",%d,%c)\n",
		option_index,
		long_options[option_index].name,
		long_options[option_index].has_arg,
		long_options[option_index].val
	);

Теперь все работает:

dron~# ./longopt2 --help
This is demo help. Try -h or --help.
option_index = 0 ("help",0,h)

dron~# ./longopt2 -h
This is demo help. Try -h or --help.
short help option

Но это еще не все фокусы :) Попробуйте поиграть с параметрами size и file:

dron~# ./longopt2 -s 10
found size without value

dron~# ./longopt2 -s10
found size with value 10

dron~# ./longopt2 --size 10
found size without value

dron~# ./longopt2 --size=10
found size with value 10

dron~# ./longopt2 -f asd
file = asd

dron~# ./longopt2 -fasd
file = asd

dron~# ./longopt2 --file asd
file = asd

dron~# ./longopt2 --file=asd
file = asd

Как видите не все так просто. У нас size задан в массиве как optional_argument, т.е. параметр с опциональным аргументом. И получается, что когда нет явного признака присвоения, т.е. когда в коротком виде значение не стоит рядом с названием, а в длинном виде нет знака "=", наш параметр не получает никакого значения.

А вот параметр file заданный как required_argument получает свое значение в любом случае. И в этом заключается отличие типов аргументов. Теперь при разработке программ надо всегда учитывать, что если вы используете опциональный аргумент, то пользователь может ошибочно ввести командную строку, и вместо значения введенного пользователем Ваша программа будет использовать значение, которое вы используете по умолчанию. Незнаю, помоему достаточно значительная проблема, о которой следует помнить.

Осталось лишь сказать о функции getopt_long_only( ), которая является полным аналогом getopt_long() за исключением того, что даже короткие параметры она пытается сравнить с длинными. Модифицируйте название функции и попробуйте запустить программу:

dron~# ./longopt3 -f 10
file = 10

dron~# ./longopt3 -f10
file = 10

dron~# ./longopt3 --file=10
file = 10

dron~# ./longopt3 -file 10
file = 10

dron~# ./longopt3 -fil 100
file = 100

Вот так вот. Данная возможность может являться и плюсом и минусом. Полезной она оказывается, когда пользователь ошибочно набирает строку и указывает вместо --opt параметр -opt. Но эта функция может сыграть злую шутку, если вдруг из коротких названий параметров получится название длинного параметра и вместо перечисления '-size' = '-s -i -z -e' у Вас получится название длинного параметра --size от -s. Вообще работа данной функции несколько загадочна и неоднозначна, поэтому сами попробуйте ее в действии. Я же думаю, что в программах со сложными входными параметрами лучше воздержаться от ее использования. Зато в программах с небольшим количеством параметров эта функция может позволить игнорировать ошибки пользователя.

Будьте бдительны и Ваши программы будут работать без ошибок !!!


Предыдущий Шаг | Следующий Шаг | Оглавление
Автор Кузин Андрей.