带命令行参数解析的C程序到底怎么写?
1 写在前面
最近工作上,遇到这样一个问题:我需要写一个C语言的程序,这个程序要求带命令行输入,之前有了解一些这方面的知识,本文将带大家好好梳理一下,希望对大家有所帮助。
2 需求分析
如上面所说,具体的功能需求是这样的:
比如:
命令行输入
./test -i in.bin -o out.bin -f -v2
或者
./test -i in.bin -o out.bin -f -v
表示的含义是: 执行test程序,输入一个in.bin文件,输出一个out.bin文件,-t表示强制执行(忽略错误), -v2表示使用第1版本功能,如果没有2只有-v,则使用默认的第1版本。
整个功能需求比较简单,核心诉求就是能够从命令行参数中筛选出输入文件名和输出文件名,以及是否强制执行(忽略错误);同时判别使用的版本号。
3 编程实现
下面我们考虑在Linux平台下,使用C语言实现上述简单需求。
3.1 main函数的另一种写法
在某些C语言教科书里面,可能只会跟你说,main函数的原型是:
int main(void);
但却没有告诉你,其实它还有另一种写法:
int main(int argc, const char *argv[]);
如果你是一个细心的编程者,一定会发现,在不带命令行参数的时候,两种main函数的原型都可以跑出预期的效果。
但是,如果像本专题中的需求,那就不得不使用第二种原型接口来玩了,因为你从运行的命令行那里输入的参数列表,就会通过main函数的argc和argv来体现。
3.2 粗暴一点的方式来实现
我们先用粗暴一点的方式来实现:直接去遍历argv列表,一个个判断即可。示例代码如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int i = 0;
char *in = NULL;
char *out = NULL;
int is_force = 0;
int version_n = 1;
int is_found_in = 0;
int is_found_out = 0;
for (i = 0; i < argc; i++) {
//show all input param
printf("param %d: %s\n", i + 1, argv[i]);
if (!strcmp(argv[i], "-i")) {
is_found_in = 1;
continue;
} else if (!strcmp(argv[i], "-o")) {
is_found_out = 1;
continue;
} else if (!strcmp(argv[i], "-f")) {
is_force = 1;
printf("is_force: %d\n", is_force);
} else if (!strncmp(argv[i], "-v", 2)) {
if (strlen(argv[i]) != 2) {
version_n = atoi(argv[i] + 2);
}
printf("version_n: %d\n", version_n);
}
if (is_found_in) {
in = argv[i];
printf("in -> %s\n", argv[i]);
is_found_in = 0;
}
if (is_found_out) {
out = argv[i];
printf("out -> %s\n", argv[i]);
is_found_out = 0;
}
}
return 0;
}
gcc编译后,运行结果如下:
./test -i in.bin -o out.bin -f -v2
param 1: ./test
param 2: -i
param 3: in.bin
in -> in.bin
param 4: -o
param 5: out.bin
out -> out.bin
param 6: -f
is_force: 1
param 7: -v2
version_n: 2
./test -i in.bin -o out.bin -f -v
param 1: ./test
param 2: -i
param 3: in.bin
in -> in.bin
param 4: -o
param 5: out.bin
out -> out.bin
param 6: -f
is_force: 1
param 7: -v
version_n: 1
基本达到预期。
3.3 优雅一点的方式来实现
上面的方面太粗暴,有没有优雅一点的方式,当然有,了解一下getopt函数。
man一下大概看看:
GETOPT(1) User Commands GETOPT(1)
NAME
getopt - parse command options (enhanced)
SYNOPSIS
getopt optstring parameters
getopt [options] [--] optstring parameters
getopt [options] -o|--options optstring [options] [--] parameters
DESCRIPTION
getopt is used to break up (parse) options in command lines for easy parsing by shell procedures, and to check for valid options. It uses the GNU getopt(3)
routines to do this.
The parameters getopt is called with can be divided into two parts: options which modify the way getopt will do the parsing (the options and the optstring in
the SYNOPSIS), and the parameters which are to be parsed (parameters in the SYNOPSIS). The second part will start at the first non-option parameter that is
not an option argument, or after the first occurrence of '--'. If no '-o' or '--options' option is found in the first part, the first parameter of the second
part is used as the short options string.
If the environment variable GETOPT_COMPATIBLE is set, or if the first parameter is not an option (does not start with a '-', the first format in the SYNOP‐
SIS), getopt will generate output that is compatible with that of other versions of getopt(1). It will still do parameter shuffling and recognize optional
arguments (see section COMPATIBILITY for more information).
Traditional implementations of getopt(1) are unable to cope with whitespace and other (shell-specific) special characters in arguments and non-option parame‐
ters. To solve this problem, this implementation can generate quoted output which must once again be interpreted by the shell (usually by using the eval com‐
mand). This has the effect of preserving those characters, but you must call getopt in a way that is no longer compatible with other versions (the second or
third format in the SYNOPSIS). To determine whether this enhanced version of getopt(1) is installed, a special test option (-T) can be used.
从这里我们可以看到它就是为解析命令行参数而诞生的。
我们来试一下用它来实现本例程中的功能需求:
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
int main(int argc, char *argv[])
{
int opt;
char *in = NULL;
char *out = NULL;
int is_force = 0;
int version_n = 1;
while ((opt = getopt(argc, argv, "i:o:fv::")) != -1) {
switch (opt) {
case 'i':
in = optarg;
printf("Option i was selected, in: %s\n", in);
break;
case 'o':
out = optarg;
printf("Option b was selected, out: %s\n", out);
break;
case 'f':
is_force = 1;
printf("Option b was selected, force: %d\n", is_force);
break;
case 'v':
if (!optarg) {
printf("Option c was selected with default value %d\n", version_n);
} else {
printf("Option c was selected with value %d\n", atoi(optarg));
}
break;
default:
fprintf(stderr, "Usage: %s [-i in] [-o out] [-f] [ -vn]\n", argv[0]);
exit(EXIT_FAILURE);
}
}
return 0;
}
编译一下,看调试结果:
./test -i in.bin -o out.bin -v
Option i was selected, in: in.bin
Option b was selected, out: out.bin
Option c was selected with default value 1
./test -i in.bin -o out.bin -v -f
Option i was selected, in: in.bin
Option b was selected, out: out.bin
Option c was selected with default value 1
Option b was selected, force: 1
$./test -i in.bin -o out.bin -v2 -f
Option i was selected, in: in.bin
Option b was selected, out: out.bin
Option c was selected with value 2
Option b was selected, force: 1
从调试的结果来看,基本是可以满足需求的。
3.4 更为优秀一点的方式来实现
其实还有一个getopt_long函数可以更加详细地描述和使用命令行参数,参考例程如下:
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
int main(int argc, char *argv[])
{
int opt;
char *in = NULL;
char *out = NULL;
int is_force = 0;
int version_n = 1;
struct option long_options[] = {
{"in", required_argument, NULL, 'i'},
{"out", required_argument, NULL, 'o'},
{"force", no_argument, NULL, 'f'},
{"version", optional_argument, NULL, 'v'},
{NULL, 0, NULL, 0}
};
while ((opt = getopt_long_only(argc, argv, "i:o:fv::", long_options, NULL)) != -1) {
switch (opt) {
case 'i':
in = optarg;
printf("Option i was selected, in: %s\n", in);
break;
case 'o':
out = optarg;
printf("Option b was selected, out: %s\n", out);
break;
case 'f':
is_force = 1;
printf("Option b was selected, force: %d\n", is_force);
break;
case 'v':
if (!optarg) {
printf("Option c was selected with default value %d\n", version_n);
} else {
printf("Option c was selected with value %d\n", atoi(optarg));
}
break;
default:
fprintf(stderr, "Usage: %s [-h] [-v] [-f file]\n", argv[0]);
exit(EXIT_FAILURE);
}
}
return 0;
}
编译之后,调试结果如下:
./test --in xxx.in --out xxx.out --force --version=2
Option i was selected, in: xxx.in
Option b was selected, out: xxx.out
Option b was selected, force: 1
Option c was selected with value 2
./test -i xxx.in -o xxx.out -f -v2
Option i was selected, in: xxx.in
Option b was selected, out: xxx.out
Option b was selected, force: 1
Option c was selected with value 2
可以看到短参数和长参数是等价的,但是需要注意的是:option的长参数输入的方式是 –xxx=yy
否则会报错:
./test --in xxx.in --out xxx.out --force --version2
Option i was selected, in: xxx.in
Option b was selected, out: xxx.out
Option b was selected, force: 1
./test: unrecognized option '--version2'
Usage: ./test [-h] [-v] [-f file]
4 经验总结
- 了解main函数还有另一个写法,以便于支持带命令行参数的传入;
- 学会使用getopt,你要搞个命令行参数解析,那不是so easy吗?
- 还有个getopt_long满足命令行参数更优雅的需求,可以深入了解下。
5 文末福利
- 最近在推广一个《致敬未来的攻城狮计划》,旨在发现并扶持一批有志于往 嵌入式开发领域 发光发热的潜力股,感兴趣的朋友,可以了解下。
- 当下正进行一个 嵌入式领域 的定向博文征稿活动,有精美礼品赠送,欢迎大家关注了解下。
- 我的技术社区(架构师李肯带你玩嵌入式),长期开展福利赠书(优质的技术图书)活动,感兴趣的朋友,可以重点关注下。