gflage的使用

转载:https://blog.youkuaiyun.com/jcjc918/article/details/50876613

写了这么多年的Linux下C/C++代码,一直使用getopt_long来解析命令行参数,同时定义一个全局的struct来保存各个命令行参数的值。虽然用得比较“繁琐”,但也安于现状。最近突然发现了Google早在多年前就开源了一个解析命令行参数的“神器”gflags。赶紧来爽一把。

安装
1、去官网下载一个最新的版本(gflags-2.1.1.tar.gz)。

2、现在流行cmake的构建方式,gflags的最新版本也改为使用cmake了。还好我最近也刚刚学习了cmake,算是跟上了潮流。

[amcool@leoox soft]$ tar xzvf gflags-2.1.1.tar.gz
[amcool@leoox soft]$ cd gflags-2.1.1
[amcool@leoox gflags-2.1.1]$ mkdir build
[amcool@leoox gflags-2.1.1]$ cd build/
[amcool@leoox build]$ cmake .. -DCMAKE_INSTALL_PREFIX=/home/amcool/local/gflags-2.1.1
[amcool@leoox build]$ make
[amcool@leoox build]$ make install

就是这么简单,安装成功了。值得注意的是,我这里新建了一个build文件夹,即采用“外部构建”的方式。这样编译过程中产生的中间文件(比如.o文件)就都放在build里,不会“污染”gflags源码,做到干干净净。

爽一把
1、既然安装好了,那赶紧来写个简单的代码来爽一把。话不多说,代码才是王道!

// demo.cpp
#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::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;
}

2、很明显,接下来就是要编译了。这里直接用g++写一行命令就可以编译了。但是既然学了cmake,那就“大材小用”一次吧。

project(demo)
cmake_minimum_required(VERSION 2.8)
set(CMAKE_VERBOSE_MAKEFILE on)

include_directories("/home/amcool/local/gflags-2.1.1/include")
link_directories("/home/amcool/local/gflags-2.1.1/lib")

add_executable(demo demo.cpp)
target_link_libraries(demo gflags pthread)

3、那当然就是编译了

[amcool@leoox demo]$ ls
CMakeLists.txt  demo.cpp
[amcool@leoox demo]$ mkdir build
[amcool@leoox demo]$ cd build
[amcool@leoox build]$ cmake ..
[amcool@leoox build]$ ls
CMakeCache.txt  CMakeFiles  cmake_install.cmake  Makefile
[amcool@leoox build]$ make
[amcool@leoox build]$ ls
CMakeCache.txt  CMakeFiles  cmake_install.cmake  demo  Makefile
[amcool@leoox build]$

设定命令行参数
1、直接运行,得到的就是我们设定的默认参数。(聪明的你,结合代码一看,就知道参数的默认值是什么了)

[amcool@leoox build]$ ./demo 
confPath = ../conf/setup.ini
port = 9090
run background ...
good luck and good bye!

2、设定参数值

i)可以用 –参数名=参数值 或者 -参数名=参数值 的方式来设定参数值。

ii)对于bool类型的参数,除了上述方式外,还可以用 –参数名 的方式设定为true(即不带值), 使用 –no参数名 的方式设定为false。为了统一,我建议都使用 上面的 第 i)种方法来设定参数。

[amcool@leoox build]$ ./demo --port=8888 --confPath=./setup.ini --daemon=true  
confPath = ./setup.ini
port = 8888
run background ...
good luck and good bye!
[amcool@leoox build]$ ./demo -port=8888 -confPath=./setup.ini -daemon=false
confPath = ./setup.ini
port = 8888
run foreground ...
good luck and good bye!
[amcool@leoox build]$ ./demo -port=8888 -confPath=./setup.ini -daemon
confPath = ./setup.ini
port = 8888
run background ...
good luck and good bye!
[amcool@leoox build]$ ./demo -port=8888 -confPath=./setup.ini -nodaemon
confPath = ./setup.ini
port = 8888
run foreground ...
good luck and good bye!
[amcool@leoox build]$

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!

版本号和帮助信息
我们一般使用程序的时候,都离不开两个参数 –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

哈,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;
}

可以来炫一把了:

[amcool@leoox build]$ ./demo --version
demo version 1.0.0.0
[amcool@leoox build]$ ./demo --help
demo: Usage : ./demo 

  Flags from /home/amcool/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



  Flags from /home/amcool/soft/gflags-2.1.1/src/gflags.cc:
    -flagfile (load flags from file) type: string default: ""
    -fromenv (set flags from the environment [use 'export FLAGS_flag1=value'])
      type: string default: ""
    -tryfromenv (set flags from the environment if present) type: string
      default: ""
    -undefok (comma-separated list of flag names that it is okay to specify on
      the command line even if the program does not define a flag with that
      name.  IMPORTANT: flags in this list that have arguments MUST use the
      flag=value format) type: string default: ""

  Flags from /home/amcool/soft/gflags-2.1.1/src/gflags_completions.cc:
    -tab_completion_columns (Number of columns to use in output for tab
      completion) type: int32 default: 80
    -tab_completion_word (If non-empty, HandleCommandLineCompletions() will
      hijack the process and attempt to do bash-style command line flag
      completion on this value.) type: string default: ""

  Flags from /home/amcool/soft/gflags-2.1.1/src/gflags_reporting.cc:
    -help (show help on all flags [tip: all flags can have two dashes])
      type: bool default: false currently: true
    -helpfull (show help on all flags -- same as -help) type: bool
      default: false
    -helpmatch (show help on modules whose name contains the specified substr)
      type: string default: ""
    -helpon (show help on the modules named by this flag value) type: string
      default: ""
    -helppackage (show help on all modules in the main package) type: bool
      default: false
    -helpshort (show help on only the main module for this program) type: bool
      default: false
    -helpxml (produce an xml version of help) type: bool default: false
    -version (show version and build info and exit) type: bool default: false
[amcool@leoox build]$

简单介绍

gflags 是 google 开源的用于处理命令行参数的项目。

安装编译

项目主页:gflags

➜  ~  git clone https://github.com/gflags/gflags.git # 下载源码
➜  ~  cd gflags
➜  gflags git:(master) ✗ mkdir build && cd build # 建立文件夹
➜  build git:(master) ✗ cmake .. # 使用 cmake 编译生成 Makefile 文件
➜  build git:(master) ✗ make # make 编译
➜  build git:(master) ✗ sudo make install # 安装库

这时 gflags 库会默认安装在 /usr/local/lib/ 下,头文件放在 /usr/local/include/gflags/ 中。

基础使用

我们从一个简单的需求来看 gflags 的使用,只要一分钟。假如我们有个程序,需要知道服务器的 ip 和端口,我们在程序中有默认的指定参数,同时希望可以通过命令行来指定不同的值。

实现如下:

#include <iostream>

#include <gflags/gflags.h>

/**
 *  定义命令行参数变量
 *  默认的主机地址为 127.0.0.1,变量解释为 'the server host'
 *  默认的端口为 12306,变量解释为 'the server port'
 */
DEFINE_string(host, "127.0.0.1", "the server host");
DEFINE_int32(port, 12306, "the server port");

int main(int argc, char** argv) {
    // 解析命令行参数,一般都放在 main 函数中开始位置
    gflags::ParseCommandLineFlags(&argc, &argv, true);
    // 访问参数变量,加上 FLAGS_
    std::cout << "The server host is: " << FLAGS_host
        << ", the server port is: " << FLAGS_port << std::endl;
    return 0;
}

OK, 写完了让我们编译运行。

➜  test g++ gflags_test.cc -o gflags_test -lgflags -lpthread # -l 链接库进行编译

➜  test ./gflags_test #不带任何参数                                                       
The server host is: 127.0.0.1, the server port is: 12306

➜  test ./gflags_test -host 10.123.78.90 #只带 host 参数
The server host is: 10.123.78.90, the server port is: 12306

➜  test ./gflags_test -port 8008 # 只带 port 参数             
The server host is: 127.0.0.1, the server port is: 8008

➜  test ./gflags_test -host 10.123.78.90 -port 8008 # host 和 port 参数
The server host is: 10.123.78.90, the server port is: 8008

➜  test ./gflags_test --host 10.123.78.90 --port 8008 # 用 -- 指定
The server host is: 10.123.78.90, the server port is: 8008

➜  test ./gflags_test --host=10.123.78.90 --port=8008 # 用 = 连接参数值
The server host is: 10.123.78.90, the server port is: 8008

➜  test ./gflags_test --help # 用 help 查看可指定的参数及参数说明
gflags_test: Warning: SetUsageMessage() never called

  Flags from /home/rookie/code/gflags/src/gflags.cc:
    .... # 略

  Flags from /home/rookie/code/gflags/src/gflags_reporting.cc:
    ..... # 略

  Flags from gflags_test.cc: #这里是我们定义的参数说明和默认值
    -host (the server host) type: string default: "127.0.0.1"
    -port (the server port) type: int32 default: 12306

看,我们不仅快速完成了需求,而且似乎多了很多看起来不错的特性。在上面我们使用了两种类型的参数,string 和 int32,gflags 一共支持 5 种类型的命令行参数定义:

  • DEFINE_bool: 布尔类型
  • DEFINE_int32: 32 位整数
  • DEFINE_int64: 64 位整数
  • DEFINE_uint64: 无符号 64 位整数
  • DEFINE_double: 浮点类型 double
  • DEFINE_string: C++ string 类型

如果你希望支持更复杂的结构,比如 list,你需要通过自己做一定的定义和解析,比如字符串按某个分隔符分割得到一个列表。

每一种类型的定义和使用都跟上面我们的例子相似,有所不同的是 bool 参数,bool 参数在命令行可以不指定值也可以指定值,假如我们定义了一个 bool 参数 debug_switch,可以在命令行这样指定:

➜  test ./gflags_test -debug_switch  # 这样就是 true
➜  test ./gflags_test -debug_switch=true # 这样也是 true
➜  test ./gflags_test -debug_switch=1 # 这样也是 true
➜  test ./gflags_test -debug_switch=false # 0 也是 false

所有我们定义的 gflags 变量都可以通过 FLAGS_ 前缀加参数名访问,gflags 变量也可以被自由修改:

if (FLAGS_consider_made_up_languages)
    FLAGS_languages += ",klingon";
if (FLAGS_languages.find("finnish") != string::npos)
    HandleFinnish();

进阶?同样 Easy

定义规范

如果你想要访问在另一个文件定义的 gflags 变量呢?使用 DECLARE_,它的作用就相当于用 extern 声明变量。为了方便的管理变量,我们推荐在 .cc 或者 .cpp 文件中 DEFINE 变量,然后只在对应 .h 中或者单元测试中 DECLARE 变量。例如,在 foo.cc 定义了一个 gflags 变量 DEFINE_string(name, 'bob', ''),假如你需要在其他文件中使用该变量,那么在 foo.h 中声明 DECLARE_string(name),然后在使用该变量的文件中 include "foo.h" 就可以。当然,这只是为了更好地管理文件关联,如果你不想遵循也是可以的。

参数检查

如果你定义的 gflags 参数很重要,希望检查其值是否符合预期,那么可以定义并注册参数的值的检查函数。如果采用 static 全局变量来确保检查函数会在 main 开始时被注册,可以保证注册会在 ParseCommandLineFlags 函数之前。如果默认值检查失败,那么 ParseCommandLineFlags 将会使程序退出。如果之后使用 SetCommandLineOption() 来改变参数的值,那么检查函数也会被调用,但是如果验证失败,只会返回 false,然后参数保持原来的值,程序不会结束。看下面的程序示例:

#include <stdint.h>
#include <stdio.h>
#include <iostream>

#include <gflags/gflags.h>

// 定义对 FLAGS_port 的检查函数
static bool ValidatePort(const char* name, int32_t value) {
    if (value > 0 && value < 32768) {
        return true;
    }
    printf("Invalid value for --%s: %d\n", name, (int)value);
    return false;
}

/**
 *  设置命令行参数变量
 *  默认的主机地址为 127.0.0.1,变量解释为 'the server host'
 *  默认的端口为 12306,变量解释为 'the server port'
 */
DEFINE_string(host, "127.0.0.1", "the server host");
DEFINE_int32(port, 12306, "the server port");

// 使用全局 static 变量来注册函数,static 变量会在 main 函数开始时就调用
static const bool port_dummy = gflags::RegisterFlagValidator(&FLAGS_port, &ValidatePort);

int main(int argc, char** argv) {
    // 解析命令行参数,一般都放在 main 函数中开始位置
    gflags::ParseCommandLineFlags(&argc, &argv, true);
    std::cout << "The server host is: " << FLAGS_host
        << ", the server port is: " << FLAGS_port << std::endl;

    // 使用 SetCommandLineOption 函数对参数进行设置才会调用检查函数
    gflags::SetCommandLineOption("port", "-2");
    std::cout << "The server host is: " << FLAGS_host
        << ", the server port is: " << FLAGS_port << std::endl;
    return 0;
}

让我们运行一下程序,看看怎么样:

#命令行指定非法值,程序解析参数时直接退出
➜  test ./gflags_test -port -2 
Invalid value for --port: -2
ERROR: failed validation of new value '-2' for flag 'port'
# 这里参数默认值合法,但是 SetCommandLineOption 指定的值不合法,程序不退出,参数保持原来的值 
➜  test ./gflags_test        
The server host is: 127.0.0.1, the server port is: 12306
Invalid value for --port: -2
The server host is: 127.0.0.1, the server port is: 12306

使用 flagfile

如果我们定义了很多参数,那么每次启动时都在命令行指定对应的参数显然是不合理的。gflags 库已经很好的解决了这个问题。你可以把 flag 参数和对应的值写在文件中,然后运行时使用 -flagfile 来指定对应的 flag 文件就好。文件中的参数定义格式与通过命令行指定是一样的。

例如,我们可以定义这样一个文件,文件后缀名没有关系,为了方便管理可以使用 .flags:

--host=10.123.14.11
--port=23333

然后命令行指定:

➜  test ./gflags_test --flagfile server.flags 
The server host is: 10.123.14.11, the server port is: 23333

棒!以后再也不用担心参数太多了~^_^

看到这里,是不是觉得 gflags 对你的项目很有帮助?用起来吧,释放超能力 :)

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值