跳到主要内容

Linux 选项参数

为了增强应用程序的通用性和灵活性,软件设计师通常会把程序中可变的部分抽离出来,让程序本身只处理业务逻辑,实现配置参数与功能代码的解耦合。在 Linux 系统编程中,通常有两种做法:

  • 通过配置文件与程序进行交互(ini、xml、json...)
  • 通过命令行选项参数进行交互(getopt)

配置文件的格式可以是常见的 ini、xml、json 或 yaml,也可以是自定义的文件格式,对于配置项较多的程序,这种方式会更方便、更直观。而命令行选项参数在 Linux 更加常见,几乎所有 Linux 命令行工具都支持(例如 ls、cd、mount 等)。

不用多说,我们都知道命令行对于 Linux 来说有多么重要,而大部分的命令行程序都是带参数的。如果是我们自己开发的命令行程序,该怎么灵活方便地为其添加选项参数呢?下面我们来看看在 Linux C 编程中如何支持参数以及对参数进行识别!

命令行参数

命令行参数的管理

Linux 应用程序是从 main 函数开始执行的,如果需要带选项参数,通常会这么定义 main 函数:

int main(int argc, char *argv[])
{
// do something
return 0;
}
  • 第1个参数 argc 表示参数的数目,包含命令本身,也就是说如果命令后面不带参数的话,argc 就等于 1。
  • 第2个参数 argv 是字符指针数组,其成员依次指向各个参数,argv[0] 指向命令本身,argv[1] 指向后面带的第1个参数,指针数组最后一个成员为 NULL,表示参数结束。

比如 ls -w 80 命令,其进程启动之后拿到的 argc 和 argv 参数内容如下:

这里要区分一下命令、选项、参数的概念。它们以空格隔开,第一个就是命令(如果使用管道,一个命令行中可以包含多个命令);选项和参数通常都是可选的,选项分为短选项和长选项,比如这里的 -w 是短选项,它对应的长选项是 --width;参数 80 是对前面的选项 -w 的描述,并非所有选项都有参数,具体由程序本身决定。参数可以紧跟选项,也可以用空格隔开,对于长选项参数还可以使用等号,所以下面几种写法是等效的:

ls -w 80
ls -w80
ls --width 80
ls --width=80

命令行参数的识别

理解了上面这点,显然我们要解析命令行的选项参数,只需要根据 main 函数传入的参数 argc 和 argv 就可以获取并处理通过命令行传入的参数了。但这样会增加程序员的工作量,并且命令行的选项参数通常是随意的,不会刻意让某个参数处于第 1 或者第 2 的位置。如果参数较多,自己解析的话很容易使代码变得臃肿,而且也不利于代码重用。那该怎么办呢?

答案就是 getopt 系列函数!通过 getopt 将参数解析部分解耦出来,数据驱动的方式看起来也很美观,我们 man 一下看看:

#include<unistd.h>  

int getopt(intargc, char * const argv[], const char *optstring);

extern char *optarg;
extern int optind, opterr, optopt;

#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_long_only 应该是 getopt 的增强版,我们先来看看最简单的 getopt 函数吧。

getopt 函数

定义

int getopt(int argc, char * const argv[], const char *optstring);

描述:getopt 是用来解析命令行选项参数的,但是只能解析短选项(如 -d 100),不能解析长选项(如 --prefix)

参数

  • argc:main() 函数传递过来的参数的个数
  • argv:main() 函数传递过来的参数的字符串指针数组
  • optstring:选项字符串,告知 getopt() 可以处理哪个选项以及哪个选项需要参数

返回:如果选项成功找到,返回选项字母;如果所有命令行选项都解析完毕,返回 -1;如果遇到选项字符不在 optstring 中,返回字符 '?';如果遇到丢失参数,那么返回值依赖于 optstring 中第一个字符,如果第一个字符是 ':' 则返回 ':',否则返回 '?' 并提示出错误信息。

下面说明 optstring 的格式意义:

char*optstring = “ab:c::”;
  • 单个字符 a  表示选项 a 没有参数;格式:-a 即可,不加参数。
  • 单字符加冒号 b: 表示选项 b 有且必须加参数;格式:-b 100 或 -b100,但 -b=100 是错误的。
  • 单字符加两冒号 c:: 表示选项 c 的参数可以有,也可以没有;格式:-c200,其它格式错误。

上面这个 optstring 在传入之后,getopt 函数将依次检查命令行是否指定了 -a,-b,-c(这需要多次调用 getopt 函数,直到其返回 -1),当检查到上面某一个参数被指定时,函数会返回被指定的参数名称(即该字母)。

此外,还有几个重要的全局变量:

  • optarg —— 指向当前选项参数(如果有)的指针。
  • optind —— 再次调用 getopt() 时的下一个 argv 指针的索引。
  • optopt —— 最后一个未知选项。
  • opterr ­——如果不希望 getopt() 打印出错信息,则只要将全局变量 opterr 设为 0 即可。

getopt_long 函数

下面再说说 getopt_long 函数,getopt_long 函数包含了 getopt 函数的功能,另外允许指定“长参数”(或者说长选项),与 getopt 函数对比,getopt_long 比其多了两个参数。

定义

int getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts,int *longindex);

描述:包含 getopt 功能,增加了解析长选项的功能(如:--prefix --help) 参数

  • longopts:指明了长参数的名称和属性
  • longindex:如果 longindex 非空,它指向的变量将记录当前找到参数符 longopts 里的第几个元素的描述,即是 longopts 的下标值

返回:对于短选项,返回值同 getopt 函数;对于长选项,如果 flag 是NULL,返回 val,否则返回 0;对于错误情况返回值同 getopt 函数。

结构体

struct option {
const char *name; /* 参数名称 */
int has_arg; /* 指明是否带有参数 */
int *flag; /* flag=NULL时,返回value;不为空时,*flag=val,返回0 */
int val; /* 用于指定函数找到选项的返回值或flag非空时指定*flag的值 */
};

has_arg 指明是否带参数值,其数值可选:

  • no_argument:表明长选项不带参数,如:--name--help
  • required_argument:表明长选项必须带参数,如:--prefix /root--prefix=/root
  • optional_argument:表明长选项的参数是可选的,如:--help–prefix=/root,其它都是错误。

getopt_long_only 函数

getopt_long_only() 函数与 getopt_long() 函数使用相同的参数表,在功能上基本一致,只是 getopt_long() 只将 --name 当作长参数,但 getopt_long_only() 会将 --name-name 两种选项都当作长参数来匹配。getopt_long_only() 如果选项 -name 不能在 longopts 中匹配,但能匹配一个短选项,它就会解析为短选项。

示例代码

我们设计一个场景,该示例程序用于输出某人说的某句话,默认输出“Hello, World!”。一共有四个选项,如下:

短选项长选项是否带参数说明
-h--help查看帮助
-v--version查看版本
-w--who设置名字
-s--say设置内容

首先引入必要的头文件:

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

许多命令行程序都支持查看帮助和版本信息,所以我们先实现好这两个函数:

#define VER_MAJOR          0
#define VER_MINOR 1
#define VER_PATCH 1

static void show_usage(const char *cmd)
{
printf("Usage: %s [options] ... \n", cmd);
printf("This is a demo for how to use options\n\n");
printf(" -h, --help display this help and exit\n");
printf(" -v, --version output version information and exit\n");
printf(" -w, --who=NAME tell me what is your NAME\n");
printf(" -s, --say=CONTENT what CONTENT do you want to say\n\n");

exit(0);
}

static void show_version(void)
{
printf("version %d.%d.%d\n", VER_MAJOR, VER_MINOR, VER_PATCH);
exit(0);
}

接下来就是要构建 option 结构体,并调用 getopt_long() 进行选项参数的识别:

int main(int argc, char *argv[])
{
int option;
char *name = NULL;
char *content = "Hello, World!";

const char * const short_options = "hvw:s:";
const struct option long_options[] = {

{ "help", 0, NULL, 'h' },
{ "version", 0, NULL, 'v' },
{ "who", 1, NULL, 'w' },
{ "say", 1, NULL, 's' },
{ NULL, 0, NULL, 0 }
};

while ((option = getopt_long(argc, argv, short_options, long_options, NULL)) != -1)
{
switch (option)
{
case 'h':
show_usage(argv[0]);
break;
case 'v':
show_version();
break;
case 'w':
name = strdup(optarg);
break;
case 's':
content = strdup(optarg);
break;
case '?':
default :
printf("Error: option invalid\n");
exit(EXIT_FAILURE);
break;
}
}

if (name)
printf("%s: ", name);

printf("%s\n", content);

return 0;
}

好啦,程序非常简单,编译运行看看效果吧!

编译:

$ gcc getopt_example.c -o getopt_example

查看版本:

$ ./getopt_example -v
version 0.1.1

查看帮助:

$ ./getopt_example -h
Usage: ./getopt_example [options] ...
This is a demo for how to use options

-h, --help display this help and exit
-v, --version output version information and exit
-w, --who=NAME tell me what is your NAME
-s, --say=CONTENT what CONTENT do you want to say

带选项参数运行:

$ ./getopt_example --who Rudy --say "该起床学习啦!"
Rudy: 该起床学习啦!