概述
Introduction of Argtable3
解析程序的命令行一直以来都是一个分散注意力的主要编程任务。Argtable3 库通过允许程序员将源代码中的命令行选项直接定义为静态的结构数组来简化作业,然后将该数组传递给argtable3库函数,这些函数就会相应地解析命令行。从命令行中提取的值直接保存到用户定义的程序变量中,主程序就可以访问这些变量。argtable3还可以从同一数组中生成命令行语法的描述,以便显示为联机帮助。根据第三条BSD许可条款,此库是免费的。
argtable3使用netbsd getopt执行实际的解析,因此它遵循POSIX实用程序约定,大多数UNIX程序和一些Windows程序都遵循此约定。它支持短选项(如-abc
和-o myfile
)、长选项(如–-scalar=7
和–-verbose
)以及无标记参数(如<file>[<file>]
)。它不支持非POSIX命令行语法,例如许多Windows程序的/x/y/z
样式选项。
快速开始
argtable3是一个单文件的ansi-c库。您需要做的只是将argtable3.c添加到项目中,并在源代码中包含argtable3.h。
例如,如果要创建一个名为util.exe
的实用程序,该实用程序具有以下命令行选项:
$> util.exe --help
Usage: util.exe [-v] [--help] [--version] [--level=<n>] [-o myfile] <file> [<file>]...
Demonstrate command-line parsing in argtable3.
--help display this help and exit
--version display version information and exit
--level=<n> foo value
-v, --verbose verbose output
-o myfile output file
<file> input files
那么你可以通过以下方式使用argtable3实现命令行分析逻辑:
#include "argtable3.h"
/* global arg_xxx structs */
struct arg_lit *verb, *help, *version;
struct arg_int *level;
struct arg_file *o, *file;
struct arg_end *end;
int main(int argc, char *argv[])
{
/* the global arg_xxx structs are initialised within the argtable */
void *argtable[] = {
help = arg_litn(NULL, "help", 0, 1, "display this help and exit"),
version = arg_litn(NULL, "version", 0, 1, "display version info and exit"),
level = arg_intn(NULL, "level", "<n>", 0, 1, "foo value"),
verb = arg_litn("v", "verbose", 0, 1, "verbose output"),
o = arg_filen("o", NULL, "myfile", 0, 1, "output file"),
file = arg_filen(NULL, NULL, "<file>", 1, 100, "input files"),
end = arg_end(20),
};
int exitcode = 0;
char progname[] = "util.exe";
int nerrors;
nerrors = arg_parse(argc,argv,argtable);
/* special case: '--help' takes precedence over error reporting */
if (help->count > 0)
{
printf("Usage: %s", progname);
arg_print_syntax(stdout, argtable, "n");
printf("Demonstrate command-line parsing in argtable3.nn");
arg_print_glossary(stdout, argtable, " %-25s %sn");
exitcode = 0;
goto exit;
}
/* If the parser returned any errors then display them and exit */
if (nerrors > 0)
{
/* Display the error details contained in the arg_end struct.*/
arg_print_errors(stdout, end, progname);
printf("Try '%s --help' for more information.n", progname);
exitcode = 1;
goto exit;
}
exit:
/* deallocate each non-null entry in argtable[] */
arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0]));
return exitcode;
}
若要使用微软Visual C++ 构建程序,可以打开Visual Studio开发者命令提示符窗口,并键入以下命令:
C:> cl.exe util.c argtable3.c
要使用gcc、mingw或cygwin构建程序,可以打开一个shell窗口并键入以下命令:
$ gcc util.c argtable3.c
如果您能够成功地构建程序并执行 Util.exe --help
查看帮助消息,这意味着您已经学习了如何将argtable3集成到程序中。在下面的部分中,我们将解释如何使用每个选项类型、如何生成帮助消息以及如何处理错误。
它是怎么工作的
argtable3提供了一组arg_xxx
结构,它支持的每种类型的参数(文字、整数、双精度、字符串、文件名等)都有一个,并且每个结构都能够处理在命令行中该参数的多次出现。此外,每个选项都可以有可选的短选项(-c
)或长选项(--scalar
)形式,它们可以互换使用。事实上,每个选项甚至可以选择多个可选的短期或长期选项,或两者兼而有之。选项也可以定义为完全没有选项标记(<file>
),在这种情况下,它们由它们在命令行上的位置标识(标记的选项可以出现在命令行的任何位置)。
要定义命令行选项,必须为所需的每种类型的参数创建一个arg_xxx
结构,并将它们整理成一个我们调用参数表的数组。参数表中结构的顺序定义了命令行选项的预期顺序,
尽管解析顺序实际上只对未标记的选项重要,但是参数表中结构的顺序定义了命令行选项的预期顺序。参数表本身只是一个void
指针数组,按照约定,每个arg_xxx
结构都有一个已知的arg_hdr
结构,这个结构作为argtable3函数用于标识结构的第一个条目。
例如,让我们考虑arg_int
结构,它用于采用整型参数的命令行选项,如-–scalar=7
。
struct arg_int
{
struct arg_hdr hdr;
int count;
int *ival;
};
结构的第一个数据成员hdr
保存argtable3库函数使用的私有数据。它包含参数的标记字符串等内容。直接访问这些数据是公开允许的,但很少有必要这样做。ival
成员变量指向一个整数数组,该数组保存从命令行中提取的值,count
给出数组中保存的值的数目。在构造arg int
时,将为ival
数组分配存储。这必须通过借助arg_int
构造器函数完成:
struct arg_int * arg_intn(
const char* shortopts,
const char* longopts,
const char *datatype,
int mincount,
int maxcount,
const char *glossary);
所有参数类型的构造器函数都以相同的方式工作:它们分配一个内存块,该内存块的头部包含一个arg_xxx
结构,后面是该结构的本地数据存储,在本例中是ival
数组的内容。因此,您不应该亲自手动实例化任何arg-xxx
结构。始终使用提供的构造函数函数来分配结构,并在完成后使用free函数将其解除分配。
继续我们的arg_int
示例,下面的代码段将构造一个整数类型选项,其形式为--scalar=<n>
,这个选项必须出现在命令行上,次数范围在3到5之间。
struct arg_int *s;
s = arg_intn(NULL, "scalar", "<n>", 3, 5, "foo value");
完成后,S
将指向一个内存块,该内存块包含arg int
结构,后跟5个元素的ival
数组。
如图所示,先不管别的,s->hdr
结构保留了对构造函数函数字符串参数的引用。s->count
变量初始化为零,因为它表示在分析命令行后存储在s->ival
数组中的有效值数。s->ival
数组的大小由s->hdr.maxcount
给出。
在这个例子中,通过向构造器函数传递一个NULL
的shortopts参数,我们省略了一个短选项形式。如果这样,我们以“k”
的形式的shortops代替“NULL”也通过了:
s = arg_intn("k", "scalar", "<n>", 3, 5, "foo value");
然后,得到的结构将是相同的,但在命令行上可以接受选项,即-k<n>
或––scalar=<n>
等效。实际上,我们可以更进一步,为短选项和长选项定义多种可选形式。可选的短选项被赋予一个由单个字符组成的字符串,而长选项被赋予一个逗号分隔的字符串。例如,
s = arg_intn("kKx", "scalar,foo", "<n>", 3, 5, "foo value");
将接受命令行上的以下任何可选的形式:-k<n> -k<n>
-x<n>
--scalar=<n>
--foo=<n>
除了arg_int
,其他让人感兴趣的arg_xxx
结构包括:
struct arg_lit
{
struct arg_hdr hdr;
int count;
};
struct arg_dbl
{
struct arg_hdr hdr;
int count;
double *dval;
};
struct arg_str
{
struct arg_hdr hdr;
int count;
const char **sval;
};
struct arg_rex
{
struct arg_hdr hdr;
int count;
const char **sval;
};
struct arg_file
{
struct arg_hdr hdr;
int count;
const char **filename;
const char **basename;
const char **extension;
};
struct arg_date
{
struct arg_hdr hdr;
const char *format;
int count;
struct tm *tm_val;
};
参数表
构造完arg xxx
结构后,我们将它们整理成参数表,如下面的示例所示,该示例定义了命令行参数:[-a] [-b] [-c] [-scalar=<n>] [-v--verbose] [-o myfile] <file> [<file>]
struct arg_lit *a = arg_litn("a", NULL, 0, 1, "the -a option");
struct arg_lit *b = arg_litn("b", NULL, 0, 1, "the -b option");
struct arg_lit *c = arg_litn("c", NULL, 0, 1, "the -c option");
struct arg_int *scal = arg_intn(NULL, "scalar", "<n>", 0, 1, "foo value");
struct arg_lit *verb = arg_litn("v", "verbose", 0, 1, "verbose output");
struct arg_file *o = arg_filen("o", NULL,"myfile", 0, 1, "output file");
struct arg_file *file = arg_filen(NULL, NULL, "<file>", 1, 2, "input files");
struct arg_end *end = arg_end(20);
void *argtable[] = {a, b, c, scal, verb, o, file, end};
-a
、-b
、-c
和-v--verbose
选项不接受参数值,因此我们对它们使用arg-lit
结构。我们将mincount
指定为0
,将maxcount
指定为1
,因为这些特定选项只在命令行上出现一次或根本不出现。
--scalar=<n>
选项接受一个整型参数,因此它使用arg_int
结构。它也会出现一次,或者根本不会出现,所以我们将mincount
指定为0
,maxcount
指定为1
。
-o myfile
和<file>
选项都引用文件名,因此我们对它们使用arg_file
结构。请注意,它是一个无标记的选项,因为它不使用短选项字符串或长选项字符串。
arg-end
结构是一个特殊的结构,因为它不代表任何命令行选项。主要是标记argtable数组的结尾,但它还存储处理命令行参数时遇到的任何解析器错误。传递给arg_end
构造函数的integer参数是它将存储的最大错误数,在本例中为20
,任何进一步的错误都将被丢弃并替换为单个错误消息“错误太多”。
我们将很快看到如何在错误报告中使用arg-end
,但首先必须确保所有参数表条目都由其构造函数函数成功分配。如果没有,argtable数组中就会有NULL
条目,这会导致问题。我们可以使用arg_null check
函数在一个步骤中检查argtable中是否有NULL
条目。如果在arg_end
结构标记的表末尾之前遇到任何空项,则返回非零。
if (arg_nullcheck(argtable) != 0)
printf("error: insufficient memoryn");
假设进展顺利,我们现在可以初始化希望分配可选参数的任何默认值。我们只需将所需的值直接写入arg_xxx
结构,因为知道argtable只会在指定有效的命令行值时覆盖它们。这里,我们分别为repeat和outfile参数设置默认值3
和-
repeat->ival[0] = 3;
outfile->filename[0] = "-";
argtable3不要求我们初始化任何默认值,如果我们在解析之前预先加载默认值,而不是在以后将默认值重新调整为缺少的值,那么对于我们的程序来说,这就简单多了。但是,您可能更喜欢后者。
解析命令行
现在我们的参数表已经完成了,我们可以使用它来解析命令行参数。我们使用arg_parse
函数来完成这项工作,它返回遇到的解析错误数。
nerrors = arg_parse(argc, argv, argtable);
如果没有错误,那么我们已经成功地解析了命令行,并且可以使用在程序的arg_xxx
结构中找到的值继续执行主要的处理任务。
if (nerrors == 0)
{
int i;
printf("-a = %dn", a->count);
printf("-b = %dn", b->count);
printf("-c = %dn", c->count);
printf("--verbose = %dn", verb->count);
if (scal->count > 0)
printf(“--scalar=%dn”, scal->ival[0]);
if (o->count > 0)
printf(“-o %sn”, o->filename[0]);
for (i = 0; i < file->count; i++)
printf(“file[%d]=%sn”, i, file->filename[i]);
};
错误处理
如果arg_parse函数报告了错误,那么我们需要将它们显示出来,因为arg_parse本身不会这样做。如前所述,arg-parse函数将遇到的错误存储在参数表的arg-end结构中。我们不需要知道arg_end结构的内部细节,我们只需调用arg_print_errors函数按遇到错误的顺序打印这些错误。
void arg_print_errors(FILE* fp, struct arg_end* end, const char* progname);
我们向函数传递一个指向参数表的arg-end
结构的指针,以及每个错误消息前面的程序名。如果不需要,程序名可以为NULL
。
If (nerrors > 0)
arg_print_errors(stdout, end, "myprog");
此示例说明了使用不正确的命令行选项调用示例程序的结果:
$ ./myprog -x -y -z --scalar=hello --verby
myprog: invalid option "-x"
myprog: invalid option "-y"
myprog: invalid option "-z"
myprog: invalid argument "hello" to option --scalar=<n>
myprog: invalid option "--verby"
myprog: missing option <file>
arg_parse
函数不打印错误消息的原因是,可以用可选参数表多次调用它来分析命令行,而不会过早显示无关的错误消息。因此,我们可以为那些具有相互排斥的命令行选项集的程序定义单独的参数表,依次尝试每个参数表,直到找到一个成功的候选者。如果所有参数表都不能满足,那么我们可以选择打印所有参数表中的错误消息,或者可能只显示最匹配参数表中的错误。在任何情况下,我们都控制着消息的显示。
显示选项语法
如果希望程序显示联机帮助,可以使用arg_print_syntax
函数显示从参数表派生的确切命令行语法。函数实际上有两种形式:
void arg_print_syntax(FILE *fp, void **argtable, const char *suffix);
void arg_print_syntaxv(FILE *fp, void **argtable, const char *suffix);
后者显示更详细的输出形式,并通过函数名末尾的v
来区分。这两个函数都显示整个参数表的语法,提供后缀参数是为了方便在输出末尾附加换行符或任何其他字符串。在详细形式中,每个参数表条目都显示其可选的用|
字符分隔的短选项和长选项,选项之后跟着其数据类型字符串。例如,
arg_int0("kKx", "scalar,foo", "<n>", "foo value");
将以详细形式显示为[-k|-K|-x|--scalar|--foo=<n>]
。而标准形式通过只显示每个参数表条目的第一个选项来缩写输出,如中的[-k <n>]
。标准表单还将参数表中的所有短选项级联起来形成开头以单个选项字符串起来的标准GNU样式(例如-a -b -c
显示为-abc
)。因此,我们前面示例中的参数表将以标准格式显示为:
[-abcv] [--scalar=<n>] [-o myfile] <file> [<file>]
上面的命令以更详细的形式表示为:
[-a] [-b] [-c] [--scalar=<n>] [-o myfile] [-v|--verbose] <file> [<file>]
请注意,可选条目自动括在方括号中,而强制参数则不是。更多接受多个实例的参数将在每个实例中显示一次,如“[]”中所示。这种情况最多出现三次,之后重复被elipisis替换,例如在“[]…”中。
arg_print_syntax
函数安全地忽略空的短选项字符串和长选项字符串,而空数据类型字符串自动替换为该arg_xxx结构的默认数据类型。默认数据类型可以通过使用空数据类型字符串而不是NULL来取消。
显示 Option Glossary
参数表的各个条目可以通过arg_print_glossary
函数在术语表布局中显示。它显示每个参数表条目的完整语法,后跟每个表条目的词汇表字符串–词汇表字符串是传递给arg_xxx
构造函数函数的最后一个参数。不显示词汇表字符串为NULL
的表条目。
void arg_print_glossary(FILE *fp, void **argtable, const char *format);
传递给arg_print_glossary
函数的格式字符串实际上是一个printf样式的格式字符串。它应该正好包含两个%s
格式参数,第一个用于控制选项语法字符串的printf
格式,第二个用于参数的词汇表字符串。典型的格式字符串为“%-25s%sn”
。格式字符串允许对显示格式进行精细控制,但由于其中的任何意外参数都会导致不可预测的结果,因此需要进行疏漏。以下是在前面的示例参数表中调用arg_print_glossary
的结果:
-a the -a option
-b the -b option
-c the -c option
--scalar=<n> foo value
-v, --verbose verbose option
-o myfile output file
<file> input files
有时,您希望向词汇表中添加额外的文本行,甚至将自己的文本放入由arg_print_syntax
生成的语法字符串中。如果愿意的话,可以在参数表字符串中添加换行符,但很快就会变得很难看。更好的方法是将arg-rem
结构添加到参数表中。它们是伪参数表条目,从某种意义上说,它们不会改变参数解析,但它们的数据类型和术语表字符串确实出现在arg_print_syntax
和arg_print_glossary
函数生成的输出中。arg_-rem
这个名字是“remark”的意思,它受到Basic语言中使用的REM
语句的启发。
清理
在程序结束时,我们需要释放分配给每个arg_xxx
结构的内存。我们可以通过单独地对它们中的每一个调用free来实现这一点,但是arg freetable
函数可以更方便地为我们实现这一点。
arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0]));
它将单步执行参数表,并代表我们对其每个元素调用free
函数。注意第二个参数sizeof(argtable)/sizeof(argtable[0])
只表示argtable数组中的元素数。完成此函数后,所有argtable数组项都将设置为NULL
。
提示:声明全局arg_xxx变量
ANSI C不允许将arg_xxx
构造函数函数放置在全局命名空间中,因此如果希望使arg_xxx
结构成为全局的,则必须在其他地方对其进行初始化。下面是一个编程技巧,用于在静态声明argtable时使用全局arg_xxx
结构。
#include <argtable3.h>
/* global arg_xxx structs */
struct arg_lit *a, *b, *c, *verb;
struct arg_int *scal;
struct arg_file *o, *file;
struct arg_end *end;
int main(int argc, char **argv)
{
/* the global arg_xxx structs are initialised within the argtable */
void *argtable[] = {
a = arg_lit0(“a”, NULL, ”the -a option”),
b = arg_lit0(“b”, NULL, ”the -b option”),
c = arg_lit0(“c”, NULL, ”the -c option”),
scal = arg_int0(NULL, ”scalar”,”<n>”, ”foo value”),
verb = arg_lit0(“v”, ”verbose, ”verbose output”),
o = arg_file0(“o”, NULL,”myfile”, ”output file”),
file = arg_filen(NULL,NULL,”<file>”,1,2, ”input files”),
end = arg_end(20),
};
...
return 0;
};
有关使用此声明样式的示例,请参阅argtable3发行版中包含的ls.c
程序。
示例程序
argtable3发行版附带了一些示例程序,这些程序为几个常见的UNIX命令实现了完整的posix兼容命令行选项。有关以下程序的源代码,请参阅argtable-3.x/example/目录:
echo [-neE] [--help] [--version] [STRING]...
ls [-aAbBcCdDfFgGhHiklLmnNopqQrRsStuUvxX1] [--author] [--block-size=SIZE] [--color=[WHEN]] [--format=WORD] [--full-time] [--si] [--dereference-command-line-symlink-to-dir] [--indicator-style=WORD] [-I PATTERN] [--show-control-chars] [--quoting-style=WORD] [--sort=WORD] [--time=WORD] [--time-style=STYLE] [-T COLS] [-w COLS] [--help] [--version] [FILE]...
mv [-bfiuv] [--backup=[CONTROL]] [--reply={yes,no,query}] [--strip-trailing-slashes] [-S SUFFIX] [--target-directory=DIRECTORY] [--help] [--version] SOURCE [SOURCE]... DEST|DIRECTORY
rm [-dfirv] [--help] [--version] <file> [<file>]...
uname [-asnrvmpio] [--help] [--version]
本文翻译自Argtable3官方教程
最后
以上就是飘逸诺言为你收集整理的Argtable3 学习(2)--教程的全部内容,希望文章能够帮你解决Argtable3 学习(2)--教程所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复