吐槽
当编写一些命令行程序时,经常需要为程序注入若干参数。在 C 语言中,命令行传参可以通过主函数的参数表获取:
int main(int argc, char** argv) { return 0; }
argc
是参数表个数,argv
则是参数的字符串数组。
对于简单的程序这就足够了,但是如果传参较为复杂,例如常用的 ffmpeg 截取视频部分生成gif:
ffmpeg -y -ss 00:00:01 -t 6 -i .mp4 -vf scale=700:-1 -f gif -r 25 some.gif
这样的参数表无论是开发还是维护都要消耗巨大的精力,还好有今天的主角 getopt
。
getopt
函数原型:
#include<unistd.h> extern char* optarg extern int opterr; extern int optind; extern int optopt; int getopt(int argc, char** argv, const char* shortopts);
getopt 函数的前两个参数接收自 main 函数的两个参数,shortopts
用来描述参数表键值对,例如 abc:d::
对应到命令行就是 -a -b -c=xxx -d
,c
后面紧跟一个冒号,表示必须为 c
指定一个值,d
后面有两个连续的冒号,表示 d
的值可选。
#include <unistd.h> #include <stdio.h> int main(int argc, char** argv) { char* shortopts = "abc:d::"; char key; while ((key = getopt(argc, argv, shortopts)) != -1) { printf("key[%c] = value[%s]\n", key, optarg); } return 0; }
*注:参数 key 和 value 之间的空格可以省略,-c xxx
和 -cxxx
都可以正确识别,但是可选参数如果设置 value,则必须时候后者写法 -dyyy
[root@fangjin test]# make main gcc -g -c main.c gcc -g -o main main.o [root@fangjin test]# ./main -a -b -c xxx -dyyy key[a] = value[(null)] key[b] = value[(null)] key[c] = value[xxx] key[d] = value[yyy]
optarg
是一个特殊的变量,它负责存储当前命令行传参解析到的 value。opterr
表示是否捕获错误将其输入到标准错误输出中,例如在传参时添加一个未定义的参数 z
:
[root@fangjin test]# ./main -a -b -c -dyyy -z
key[a] = value[(null)]
key[b] = value[777]
key[c] = value[xxx]
key[d] = value[yyy]
./main: invalid option -- 'z'
Unknown option: z
如果在代码开头将 opterr
设置为 0
,则不会有最后两条错误日志输出。
optind
用来表示 argv
参数表的下一个读取的索引位置,optopt
表示不再指定键值对之外的剩余参数个数。
#include <unistd.h> #include <stdio.h> int main(int argc, char** argv) { opterr = 0; char* shortopts = "abc:d::"; char key; while ((key = getopt(argc, argv, shortopts)) != -1) { switch (key) { case 'a': case 'b': case 'c': case 'd': printf("key[%c] = value[%s]\n", key, optarg); break; case '?': printf("Unknown option: %c\n", (char)optopt); break; } } for (int i = optind; i < argc; i++) { printf("%s\n", argv[i]); } return 0; }
[root@fangjin test]# ./main -a -b -c xxx -dyyy -z 123 456 789
key[a] = value[(null)]
key[b] = value[(null)]
key[c] = value[xxx]
key[d] = value[yyy]
Unknown option: z
123
456
789
借助 optind
可以获取到未被表达式捕获的剩余参数,此处有一个容易搞混的地方,注意看下面的传参:
[root@fangjin test]# make main; ./main -a -b 789 -cxxx -dyyy -z 123 456
make: 'main' is up to date.
key[a] = value[(null)]
key[b] = value[(null)]
key[c] = value[xxx]
key[d] = value[yyy]
Unknown option: z
789
123
456
查看上面代码的 [21-23]
行,789
这个参数并没有在参数表的最后,为什么仍可以被 for 循环遍历出来呢?因为 getopt 函数在执行时,会调整 args 中参数的顺序。
getopt_long
getopt_long 与 getopt 无太大差别,只是支持了长选项 --name=zhangsan --age=12
。
int getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex);
除了在函数调用上发生了一点变化,其余完全相同,本文不再赘述:
#include <stdio.h> #include <getopt.h> int main(int argc, char** argv) { int opt_index = 0; struct option opts[] = { { "name", required_argument, NULL, 'm' }, { "age", required_argument, NULL, 's' }, }; char* shortopts = "m:s:"; char key; while ((key = getopt_long(argc, argv, shortopts, opts, &opt_index)) != -1) { switch (key) { case 'm': case 's': printf("key[%c] = value[%s]\n", key, optarg); break; case '?': printf("Unknown option: %c\n", (char)optopt); break; } } return 0; }
[root@fangjin test]# ./main --name zhangsan --age 16
key[m] = value[zhangsan]
key[s] = value[16]