0
点赞
收藏
分享

微信扫一扫

一个灵活的程序应该是可配置的


这段时间在公司充当救火员的角色,拯救了一个快要腐烂的项目。其中做的一个工作就是将其变成可配置的,这样可以增加程序的灵活性,如果想改变程序的行为,只需修改参数即可,而不是重新编译。

一个灵活的程序应该是可配置的_命令行选项参数

首先我们需要把程序中可变的部分抽离出来,程序本身只处理业务逻辑,实现配置参数与功能代码的解耦合。在 Linux 环境编程中,通常有两种做法:

  • 通过配置文件与程序进行交互
  • 通过命令行选项参数进行交互

配置文件的格式可以是常见的 ini、xml、json,也可以是自定义的文件格式,对于配置项较多的程序,这种方式会更方便、更直观。而命令行选项参数在 Linux 更加常见,几乎所有 Linux 命令行工具都支持。本文就给大家讲解,如何让一个命令行程序支持选项参数。

命令行参数的管理

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

int main(int argc, char *argv[])
{
/* do something */
}

  • 第1个参数 argc 表示参数的数目,包含命令本身,也就是说如果命令后面不带参数的话,argc 就等于 1。
  • 第2个参数 argv 是字符指针数组,其成员依次指向各个参数,​​argv[0]​​​ 指向命令本身,​​argv[1]​​ 指向后面带的第1个参数,指针数组最后一个成员为 NULL,表示参数结束。

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

一个灵活的程序应该是可配置的_命令行选项参数_02

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

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

命令行参数的识别

理解了上面这点,显然我们要解析命令行的选项参数,只需要根据 argc 的值对 argv 进行拆解即可。但这样会增加程序员的工作量,并且命令行的选项参数通常是随意的,不会刻意让某个参数处于第1或者第2的位置,因此,使用 Linux 为我们提供的 ​​getopt()​​​ 和 ​​getopt_long()​​ 函数进行命令行参数的识别是更好的选择。

短选项参数

​getopt()​​ 函数用于解析命令行参数,其函数原型如下:

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

参数

描述

argc

命令参数的个数

argv

指向这些参数的数组

optstring

所有可能的参数字符串

返回值

选项字符

返回识别成功的选项字符

‘?’

遇到无效的选项字符或缺少参数时

-1

当没有其他参数供解析或者出错时

第3个参数 optstring 可以是下列元素:

  • 单个字符:表示该选项不带参数;
  • 单个字符后接一个冒号:表示该选项后必须跟一个参数,参数可以紧跟在选项后面,也可以以空格隔开;
  • 单个字符后接两个冒号:表示该选项后可以跟一个参数,参数必须紧跟在选项后面,不能以空格隔开。

PS:两个冒号的用法比较奇特,而且跟长选项参数配合得不好,建议谨慎使用!

为了完成参数识别,Linux 预设了几个全局变量, ​​getopt()​​ 执行后的内容会暂存于此。

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

  • optarg:指向当前选项参数(如果有的话)的指针
  • optind:再次调用​​getopt()​​ 时的下一个 argv 指针的索引
  • opterr:存储错误代码
  • optopt:存储未知或出错(比如缺少参数)的选项

默认情况下,getopt 函数会重新排列命令行参数的顺序,所有不可知或错误的命令行参数都排列到最后,当没有其他参数供解析或者出错时,getopt 将返回 -1,同时 optind 中将存放第一个未知或出错选项的下标。

长选项参数

短选项参数言简意赅,对于选项较少的程序是极好的。然而,毕竟只有26个英文字母,选项过多就显得不够用了,而且单个字符很容易重复,造成表意不明。因此,一套善解人意的选项参数,往往需要同时提供短选项和长选项,兼顾实用性和明义性。

Linux 为我们提供了 ​​getopt_long()​​​ 和 ​​getopt_long_only()​​ 两个函数,其函数原型如下:

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);

参数

描述

argc

命令参数的个数

argv

指向这些参数的数组

optstring

短选项参数字符串

longopts

长选项参数标识

longindex

记录长选项的索引

返回值

选项字符

返回识别成功的选项字符

‘?’

遇到无效的选项字符或缺少参数时

-1

当没有其他参数供解析或者出错时

可以看到,前3个参数与 ​​getopt()​​ 是一样的。第4个参数 longopts 是我们要构建的长选项结构体数组,它包含选项的标识及其对应关系。第5个参数可用于记录长选项的索引,如果没有特别的选项参数结构,设置为 NULL 即可。

option 结构体的定义如下:

struct option
{
const char *name; /* 长选项名 */
int has_arg; /* 0(no_argument)表示该参数后面不跟参数值 */
/* 1(required_argument)表示该参数后面一定要跟个参数值 */
/* 2(optional_argument)表示该参数后面可以跟,也可以不跟参数值 */
int *flag; /* 用于决定getopt_long()的返回值 */
/* 如果flag是NULL(通常情况),则返回对应的val值(下一个成员) */
/* 如果flag不是NULL,则将val值赋给flag所指向的内存,并返回0 */
int val; /* 和flag一起决定返回值 */
};

​getopt_long_only()​​​ 的用法和 ​​getopt_long()​​​ 相同,唯一的区别在输入长选项的时候可以不用输入​​--​​​ 而使用 ​​-​​。

使用示例

纸上得来终觉浅,绝知此事要躬行。下面通过一个简单的示例,来看看命令行选项参数应该怎么实现。

一个灵活的程序应该是可配置的_命令行选项参数_03

我们设计一个场景,该示例程序用于输出某人说的某句话,默认输出“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;
}

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

一个灵活的程序应该是可配置的_getopt_long_04


举报

相关推荐

0 条评论