介绍,并与其他命令行标志库进行比较

命令行参数是用户运行可执行文件时在命令行上指定的标志。
在命令行中fgrep -l -f /var/tmp/foo johannes brahms -l和-f就是命令行参数,通常用户传入给应用程序的参数,或者接收到的参数,在这个例子中,-l没有接收参数,-f接收了一个字符串参数。用户可以用库来解析命令行,并且把参数保存到数据结构中。

Gflags是google内部使用的命令行标志库,与其他库(getopt()) 不同,参数不仅仅可以在main() 这样的地方列出,还可以定义在代码的任何地方。

这就意味着单个源代码文件可以定义标志,任何应用程序只要在文件中引入该文件,那么应用程序将获得该标志,并且gflags库将合理的处理这些标志。

由于这种技术,灵活性显著提高,代码重用更容易。然而,两个文件如果定义相同标志,将会存在隐患,当它们链接在一起时会产生错误。

本文其余部分描述了如何使用命令行标记库。这是一个C++库,所以例子是C++。但是,有一个Python端口具有相同的功能,这个讨论直接转换为Python。

下载安装

Gflags可以在GitHub中下载,使用如下命令:
git clone https://github.com/gflags/gflags.git
构建和安装说明在install文件中,gflags的安装包包括了配置文件和主流的构建系统,例如:

要学会怎么使用主流的构建系统pkg-config, CMake, and Bazel.

定义:在程序中定义标志

定义标志很容易:只需要为希望标志的类型使用适当的宏,如gflags/gflags.h底部定义的。

#include <gflags/gflags.h>
DEFINE_bool(big_menu, true, 
"Include 'advanced' options in the menu listing");
DEFINE_string(languages, "english,french,german",
    "comma-separated list of languages to offer in the 'lang' menu");

DEFINE_bool 定义了bool类型的标志。一下是支持的类型:
• DEFINE_bool: bool类型
• DEFINE_int32: 32-bit 整型
• DEFINE_int64: 64-bit 整型
• DEFINE_uint64: unsigned 64-bit 整型
• DEFINE_double: double
• DEFINE_string: C++ string

声明:在不同文件中使用标志

当你使用一个flag时,在另一个文件中用宏定义DECLARE_type定义一个标记。举一个例子:
在bar.cc中想访问big_menu这个flag,那么就在bar.cc文件头中声明
DECLARE_bool(big_menu, true , “Include ‘advanced’ options in the menu listing”);

这句宏相当于声明一个 FLAGS_big_menu 的标识变量。

注意:
如果单独在一个文件中使用,那就直接定义这个标记,但是在多文件中需要访问这些标记时,可以采用如下的方法:

标志跨多个文件,一般在一个.h文件中声明这些flags,如果想访问这些标记,就直接引用#include ,#include将在头文件和实现文件之间建立依赖关系,使得标志成为全局变量。

访问标志

前缀FLAGS_ 是被预先准备好,所以程序把这些以FLAGS_开头标志当成普通变量。宏定义了两个变量:FLAGS_big_menu菜单(Bool)和FLAGS_languages语言(C++字符串)。
你可以像任何其他变量一样读和写标志:

#include <gflags/gflags.h>
DEFINE_bool(big_menu, true, 
"Include 'advanced' options in the menu listing");
DEFINE_string(languages, "english,french,german",
    "comma-separated list of languages to offer in the 'lang' menu");
   if(FLAGS_big_menu)
      cout<<"Include 'advanced' options in the menu listing";
   if (FLAGS_languages.find("finnish") != string::npos)
     HandleFinnish();

您也可以通过gflags.h中的特殊函数获取和设置标志值。不过,很少这么用。

注册标志验证器:健全性检查标记

在定义标记后,可以注册一个标记验证器,当标记从命令行解析之后,每当调用SetCommandLineOption()来设置标志时,将会出发标记验证器, 如果标记值有效,返回true,否则返回false,并且该标记将保留之前设置的值, 如果刚开始解析,调用验证器返回false,此时ParseCommandLineFlags 方法将失败。
如下是例子:
/* 定义一个验证器方法*/

static bool ValidatePort(const char* flagname, int32 value) 
{
   if (value > 0 && value < 32768)   // value is ok
     return true;
   printf("Invalid value for --%s: %d\n", flagname, (int)value);
   return false;
}
DEFINE_int32(port, 0, "What port to listen on");// 定义标记
DEFINE_validator(port, &ValidatePort);// 给标记注册验证器
通过注册全局函数,在定义flag之后,确保这个注册发生main()函数命令行被解析之前。
上面的DEFINE_validator宏调用RegisterFlagValidator()方法,如果注册成功返回true,失败返回false  

a):第一个参数没有引用命令行标记
b):这个标记已经被注册了别的验证器了
返回值可作为全局静态bool变量 _validator_registered

如何设置标志

可执行程序解析命令行参数,并且合理的设置FLAGS_* 参数,基于命令行上可见的非默认值。使用getopt()需要 调用getopt库,使用gflags就是一个函数调用,开销要小的多 。
gflags::ParseCommandLineFlags(&argc, &argv, true);
通常,在主函数main()的开头,argc和argv恰当的把参数传入main()中,这个程序可以修改这些参数,这就是为什么它们的指针被传入了。
ParseCommandLineFlags中最后一个参数称为“remove_flags”。如果是true, ParseCommandLineFlags函数移除标记和它们在argv中的参数,并且适当的修改argc,这时,函数在函数调用之后,argv将只保留命令行参数,而不保留命令行标记。
如果为false,ParseCommandLineFlags函数将保留argc不变,但是将重新整理一下在argv中的参数,这样方便标记都在开头处。随便举一个例子:
如输入“/bin/foo” “arg1” “-q” “arg2” ParseCommandLineFlags重新排列一下argv变成: “/bin/foo”, “-q”, “arg1”, “arg2”,这样方便读取。本例中ParseCommandLineFlags将索引返回到argv中,argv中保存第一个命令行参数,即,超过最后一个标志的索引。(在这个例子中,它会返回2,因为argv(2)指向arg1。)
在这两种情况下,根据命令行中传入的内容修改FLAGS_*变量。

在命令行上设置标志

1. 可以用 –参数名=参数值 或者 --参数名=参数值 的方式来设定参数值.
例如,在百度的bfs部署脚本中,就是使用–参数名=参数值的方式

echo '--default_replica_num=3' >> bfs.flag      # 默认复制数 3 
echo '--chunkserver_log_level=2' >> bfs.flag    # chunkserver 日志级别是 2
echo '--blockreport_interval=2' >> bfs.flag     # 块报告间隔 2

2. 对于bool类型的参数,除了上述方式外,还可以用 –参数名 的方式设定为true(即不带值), 使用 –no参数名 的方式设定为false。为了统一,我建议都使用 上面的 第 i)种方法来设定参数。
3. 如果工程比较大,配置项非常多,也就是说命令行参数很多,那你每次启动都要一个一个的输入,那岂不是很麻烦?
gflags已经帮我们解决了,用 –-flagfile=命令行文件 的方式就可以了。你接着往下看,就明白了。param.cmd就是上面说的命令行文件。

[amcool@leoox build]$ vi param.cmd 
--port=8888
--confPath=./setup.ini
--daemon=true
[amcool@leoox build]$ ./demo --flagfile=param.cmd
confPath = ./setup.ini
port = 8888
run background ...
good luck and good bye!
[amcool@leoox build]$

4. 从环境变量读入参数值
gflags另外还给我们提供了 –fromenv 和 –tryfromenv 参数,通过这两个参数,我们的程序可以从环境变量中获取到具体的值。两者有什么不一样呢。你看到他们的区别仅仅是有无“try”,聪明的你一定猜到了。
–fromenv 从环境变量读取参数值 –fromenv=port,confPath 表明要从环境变量读取port,confPath两个参数的值。但是当无法从环境变量中获取到的时候,会报错,同时程序退出。

注意:gflags的变量名是 FLAGS_我们定义的参数名,开篇的代码里,估计细心的你已经发现了】
–tryfromenv 与–fromenv类似,当参数的没有在环境变量定义时,不退出。

[amcool@leoox build]$ ./demo --fromenv=port,confPath
ERROR: FLAGS_confPath not found in environment
ERROR: FLAGS_port not found in environment
[amcool@leoox build]$ ./demo --tryfromenv=port,confPath
confPath = ../conf/setup.ini
port = 9090
run background ...
good luck and good bye!
[amcool@leoox build]$ export FLAGS_confPath=./loveyou.ini
[amcool@leoox build]$ export FLAGS_port=36888   
[amcool@leoox build]$ env | grep FLAGS
FLAGS_port=36888
FLAGS_confPath=./loveyou.ini
[amcool@leoox build]$          
[amcool@leoox build]$ ./demo --fromenv=port,confPath     
confPath = ./loveyou.ini
port = 36888
run background ...
good luck and good bye!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

5. 版本号和帮助信息
我们一般使用程序的时候,都离不开两个参数 –version 和 –help。来看看上面实现的demo能否支持呢?

[amcool@leoox build]$ ./demo --version
demo
[amcool@leoox build]$ ./demo --help
demo: Warning: SetUsageMessage() never called

 Flags from /home/thrift/program/gflags/demo/demo.cpp:
 -confPath (program configure file.) type: string
 default: "../conf/setup.ini"
 -daemon (run daemon mode) type: bool default: true
 -port (program listen port) type: int32 default: 9090
1
2
3
4
5
6
7
8
9
10

哈,help支持了,但是version没支持,而且help信息里面还有waring。没关系,我们可以用 SetVersionString() 和 SetUsageMessage() 方法来满足需求。修改后的代码如下:

注意:SetVersionString() 和 SetUsageMessage() 一定要在 ParseCommandLineFlags() 之前设定。

#include <iostream>
#include <gflags/gflags.h>
using namespace std;
DEFINE_string(confPath, "../conf/setup.ini", "program configure file.");
DEFINE_int32(port, 9090, "program listen port");
DEFINE_bool(daemon, true, "run daemon mode");

int main(int argc, char** argv)
{
  gflags::SetVersionString("1.0.0.0");
  gflags::SetUsageMessage("Usage : ./demo ");
  gflags::ParseCommandLineFlags(&argc, &argv, true);

  cout << "confPath = " << FLAGS_confPath << endl;
  cout << "port = " << FLAGS_port << endl;

  if (FLAGS_daemon) {
    cout << "run background ..." << endl;
  }
  else {
    cout << "run foreground ..." << endl;
  }

  cout << "good luck and good bye!" << endl;

  gflags::ShutDownCommandLineFlags();
  return 0;
}

更改默认标志值

特殊标记

API

杂记

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐