如何使用gflags(以前的Google Commandline标志)
(截至 2018年11月12日星期一)
目录
介绍
下载和安装
使用CMake声明对gflags的依赖
使用Bazel声明对gflags的依赖
定义:在程序中定义标志
访问标志
声明:在不同文件中使用标志
RegisterFlagValidator:完整性检查标志值
把它放在一起:如何设置标志
在命令行上设置标志
更改默认标志值
特殊标志
API
杂项说明
问题和功能请求
介绍和与其他命令行标志库的比较
命令行标志是用户在运行可执行文件时在命令行上指定的标志。在命令中
fgrep -l -f / var / tmp / foo johannes brahms
-l并且-f /var/tmp/foo是两个命令行标志。(johannes并且brahms,不以破折号开头,是命令行参数。)
通常,应用程序列出允许用户传入的标志,以及它们采用的参数 - 在此示例中, -l不带参数,并将-f字符串(特别是文件名)作为参数。用户可以使用库来帮助解析命令行并将标志存储在某些数据结构中。
Gflags是Google中使用的命令行标记库,它与其他库不同,例如getopt(),标志定义可以分散在源代码周围,而不仅仅列在一个地方,例如main()。实际上,这意味着单个源代码文件将定义和使用对该文件有意义的标志。在该文件中链接的任何应用程序都将获得标志,gflags库将自动适当地处理该标志。
由于这种技术,灵活性和代码重用的简易性得到了显着提高。但是,存在两个文件定义相同标志的危险,然后在它们链接在一起时发出错误。
本文档的其余部分介绍了如何使用commandlineflag库。它是一个C ++库,所以示例都在C ++中。但是,有一个具有相同功能的Python端口,此讨论直接转换为Python。
下载和安装
gflags库可以从GitHub下载。您可以使用以下命令克隆项目:
git clone https://github.com/gflags/gflags.git
INSTALL文件中提供了构建和安装说明 。gflags包的安装包括流行的构建系统的配置文件,例如pkg-config, CMake和Bazel。
使用CMake声明对gflags的依赖
在使用CMake构建系统的项目中使用gflags 很容易。您可以要求外部安装gflags包并使用CMake的find_package命令找到它,或者在项目的源代码树中包含gflags项目作为子树或子模块,并使用CMake的add_subdirectory命令添加目录。
要使用外部gflags安装,请将以下CMake代码添加到您的CMakeLists.txt文件中。
找到gflags安装。的gflags_DIR变量必须设置到<前缀> / LIB / cmake的/ GFLAGS含有GFLAGS-config.cmake文件,如果<前缀>是一个非标准位置目录。否则,CMake应该自动找到gflags安装。
find_package(需要gflags)
要请求链接的特定导入gflags库目标,请使用COMPONENTSfind_package命令的选项。例如,要强制使用单线程静态库,请使用该命令
find_package(gflags COMPONENTS nothreads_static)
请注意,当安装的gflags包不包含请求的库时,这将引发致命错误。因此,建议仅在必须使用特定库时指定要查找的特定组件。否则,gflags-config.cmake模块将为您选择合适且可用的库。默认情况下,如果可用,则选择具有共享链接的多线程gflags库。
当gflags项目的源树作为子树或子模块包含在项目的“gflags”目录中时,请替换上面的find_package命令add_subdirectory(gflags)。请参阅gflags/CMakeLists.txt 文件顶部,以获取可在此命令之前设置的可用CMake变量列表,以配置gflags库的构建。默认构建设置是单线程静态库的构建,不需要安装任何gflags子项目产品。
最后,添加可执行构建目标,该目标使用gflags来解析依赖于导入的gflags库目标的命令参数:
add_executable(foo main.cc)
target_link_libraries(foo gflags :: gflags)
使用Bazel声明对gflags的依赖
要在使用Bazel作为构建工具的项目中使用gflags,请在WORKSPACE文件中添加以下行(另请参阅git_repository的 Bazel文档):
git_repository(
name =“com_github_gflags_gflags”,
remote =“https://github.com/gflags/gflags.git”,
tags =“v2.2.2”
)
然后,您可以添加@com_github_gflags_gflags//:gflags到规则或规则的deps部分 ,并将其包含在源代码中。这使用了启用了多线程的共享gflags库。要使用单线程共享gflags库,请改用依赖项 。cc_binarycc_library#include "gflags/gflags.h"@com_github_gflags_gflags//:gflags_nothreads
例如,请参阅BUILDgflags / example项目的以下规则:
cc_binary(
name =“foo”,
srcs = [“main.cc”],
deps = [“// external:gflags”],
)
定义:在程序中定义标志
定义标志很简单:只需使用适合您希望标志类型的宏,如底部所定义 gflags/gflags.h。这是一个示例文件, foo.cc:
#include <gflags / gflags.h>
DEFINE_bool(big_menu,true,“菜单列表中包含'高级'选项”);
DEFINE_string(语言,“英语,法语,德语”,
“在'lang'菜单中提供的以逗号分隔的语言列表”);
DEFINE_bool定义一个布尔标志。以下是支持的类型:
DEFINE_bool:布尔值
DEFINE_int32:32位整数
DEFINE_int64:64位整数
DEFINE_uint64:无符号的64位整数
DEFINE_double:加倍
DEFINE_string:C ++字符串
请注意,没有像列表这样的“复杂”类型:我们示例中的“languages”标志是字符串列表,但是定义为“string”类型,而不是“list_of_string”或类似字符串。这是设计的。我们宁愿只使用简单类型的标志,并允许复杂的,任意的解析例程来解析它们,而不是尝试将逻辑放在标记库中。
所有DEFINE宏都使用相同的三个参数:标志的名称,默认值以及描述其用法的“帮助”字符串。当用户使用--help标志运行应用程序时,将显示“help”字符串。
您可以在可执行文件的任何源代码文件中定义标志。只定义一次标志!如果要在多个源文件中访问标志,请在一个文件中对其进行定义,并在其他文件中对其进行DECLARE。更好的是,将其定义并将其foo.cc删除foo.h; 然后每个人#includes foo.h都可以使用国旗。
在库中而不是在main()中定义标志是强大的,但确实有一些成本。一个是库的标志可能没有良好的默认值,例如,如果该标志包含某些环境中可能不存在的文件名。要缓解此类问题,您可以使用标志验证程序来确保无效标志值的提示通知(以崩溃的形式)。
请注意,虽然在这个库的大部分功能在定义 google命名空间DEFINE_foo(和 DECLARE_foo,以下),应始终在全局命名空间。
访问flag
程序可以使用所有已定义的标志作为正常变量,前缀为FLAGS_前缀。在上面的例子中,宏定义了两个变量FLAGS_big_menu (bool)和FLAGS_languages(一个C ++字符串)。
您可以像任何其他变量一样读取和写入标志:
if(FLAGS_consider_made_up_languages)
FLAGS_languages + =“,klingon”; //由--consider_made_up_languages暗示
if(FLAGS_languages.find(“finnish”)!= string :: npos)
HandleFinnish();
您还可以通过特殊函数获取和设置标志值 gflags.h。不过,这是一个罕见的用例。
声明:在不同文件中使用标志
只有DEFINE在文件顶部标记-ed时,才能以上一节的方式访问标志。如果不是,您将收到“未知变量”错误。
DECLARE_type如果要使用在另一个文件中定义的标志,则可以使用该宏。例如,如果我正在写bar.cc但想要访问big_menu,flag,我会把它放在顶部bar.cc:
DECLARE_bool(big_menu);
这在功能上等同于说extern FLAGS_big_menu。
请注意,这样的extern声明会在您的文件和定义big_menu 标志的文件之间引入依赖关系:foo.cc在本例中。在大型项目中,这种隐式依赖关系可能难以管理。因此,我们建议遵循以下准则:
如果您在foo.cc其中定义了一个标志,或者根本不对其进行DECLARE,只在相关的测试中对其进行DECLARE,或者仅对其进行DECLARE foo.h。
当只需要标志时,你应该去do-not-DECLARE路由foo.cc,而不是任何其他文件。如果要修改相关测试文件中的标志值以查看它是否按预期运行,请在foo_test.cc 文件中对其进行DECLARE 。
如果该标志确实跨越多个文件,则在相关.h文件中对其进行DECLARE #include,.h如果要访问该标志, 则将其他文件作为文件。在 #include将两个文件之间明显的相关性。这会使标志成为全局变量。
RegisterFlagValidator:完整性检查标志值
在定义标志后,您可以选择使用该标志注册验证器功能。如果这样做,在从命令行解析标志之后,并且每当通过调用更改其值时SetCommandLineOption(),将使用新值作为参数调用 验证器函数。如果标志值有效,验证器函数应返回“true”,否则返回false。如果函数对于标志的新设置返回false,则该标志将保留其当前值。如果它为默认值返回false,则ParseCommandLineFlags将死亡。
以下是此功能的示例用法:
static bool ValidatePort(const char * flagname,int32 value){
if(value> 0 && value <32768)//值正常
返回true;
printf(“ - %s:%d \ n”的值无效,flagname,(int)值);
返回false;
}
DEFINE_int32(port,0,“要监听的端口”);
DEFINE_validator(port,&ValidatePort);
通过在全局初始化时(在DEFINE_int32之后)进行注册,我们确保在开始时解析命令行之前进行注册main()。
上面使用的DEFINE_validator宏调用 RegisterFlagValidator()函数,如果注册成功,则返回true。如果注册失败,则返回false,因为a)第一个参数未引用命令行标志,或b)已为此标志注册了不同的验证器。返回值可用作名为的全局静态布尔变量 <flag>_validator_registered。
如何把标志表在一起:如何设置标志
最后一段是告诉可执行文件处理命令行标志,并根据命令行上显示的内容将FLAGS_*变量设置为适当的非默认值。这相当于getopt()getopt库中的调用,但使用的开销要少得多。实际上,它只是一个函数调用:
gflags :: ParseCommandLineFlags(&argc,&argv,true);
通常,此代码位于开头main()。 argc并argv完全按照传递给 main()。这个例程可能会修改它们,这就是为什么传入指针的原因。
最后一个参数称为“remove_flags”。如果为true,则 ParseCommandLineFlags从中删除标志及其参数argv,并进行argc 适当修改。在这种情况下,在函数调用之后, argv将只保存命令行参数,而不是命令行标志。
另一方面,如果remove_flags是假的,那么 ParseCommandLineFlags将保持argc不变,但是会重新排列argv中的参数,以便标志都在开头。例如,如果输入是"/bin/foo" "arg1" "-q" "arg2"(合法但很奇怪),则函数将重新排列 argv以便读取"/bin/foo", "-q", "arg1", "arg2"。在这种情况下,ParseCommandLineFlags 将索引返回到包含第一个命令行参数的argv:即,超过最后一个标志的索引。(在这个例子中,它会返回2,因为argv[2]指向arg1。)
在任何一种情况下,都会FLAGS_*根据命令行传入的内容修改变量。
在命令行上设置标志
你创建一个标志而不是编译时常量的原因是用户可以在命令行上指定一个非默认值。以下是他们如何为链接的应用程序执行此操作foo.cc:
app_containing_foo --nobig_menu -languages =“中文,日文,韩文”......
这设置,FLAGS_big_menu = false;并 FLAGS_languages = "chinese,japanese,korean"在 ParseCommandLineFlags运行时。
请注意将布尔标志设置为false的非典型语法:在其名称前面加上“no”。如何指定标志有一定的灵活性。以下是指定“语言”标志的所有方法的示例:
app_containing_foo --languages="chinese,japanese,korean"
app_containing_foo -languages="chinese,japanese,korean"
app_containing_foo --languages "chinese,japanese,korean"
app_containing_foo -languages "chinese,japanese,korean"
对于布尔标志,可能性略有不同:
app_containing_foo --big_menu
app_containing_foo --nobig_menu
app_containing_foo --big_menu=true
app_containing_foo --big_menu=false
(以及所有这些的单破折号变体)。
尽管有这种灵活性,我们建议只使用一种形式: --variable=value非布尔标志和 --variable/--novariable布尔标志。这种一致性将使您的代码更具可读性,并且也是某些特殊用例(如flagfiles)所需的格式。
在命令行上指定一个在可执行文件中某处未被定义的标志是一个致命的错误。如果由于某种原因需要该功能 - 比如说你想为几个可执行文件使用相同的标志集,但并非所有标志都在列表中的每个标志 - 你可以指定--undefok抑制错误。
与getopt()一样,--它本身将终止标志处理。因此foo -f1 1 -- -f2 2,f1被认为是一面旗帜,但-f2事实并非如此。
如果多次指定一个标志,则只使用最后一个规范; 其他人被忽略了。
请注意,标志不具有单字母同义词,就像它们在getopt库中一样,也不允许在单个短划线后面“组合”标记,如ls -la。
更改默认标志值
有时会在库中定义标志,并且您希望在一个应用程序中更改其默认值,而不是在其他应用程序中更改。这样做很简单:main()在调用之前,只需为该标志分配一个新值ParseCommandLineFlags():
DECLARE_bool(lib_verbose); // mylib有一个lib_verbose标志,默认为false
int main(int argc,char ** argv){
FLAGS_lib_verbose = true; //在我的应用程序中,默认情况下我想要一个详细的lib
ParseCommandLineFlags(...);
}
对于此应用程序,用户仍可以在命令行上设置标志值,但如果不是,则标志的值将默认为true。
特殊标志
commandlineflags模块本身定义了一些标志,并且可供所有使用commandlineflags的应用程序使用。这些分为三类。首先是“报告”标志,当找到它们时,会导致应用程序打印一些有关自身的信息并退出。
--help 显示所有文件中的所有标志,按文件排序,然后按名称排序; 显示flagname,其默认值及其帮助字符串
--helpfull 与-help相同,但明确要求所有标志(以防 - 将来帮助更改)
--helpshort 仅显示与可执行文件同名的文件的标志(通常是包含的文件main())
--helpxml 喜欢--help,但输出是xml,以便于解析
--helpon=FILE 仅显示在FILE中定义的标志。*
--helpmatch=S 仅显示* S *中定义的标志。*
--helppackage 显示在同一目录中的文件中定义的标志 main()
--version 打印可执行文件的版本信息
其次是影响如何解析其他标志的标志。
--undefok=flagname,flagname,... 对于作为参数列出的那些名称--undefok,可以抑制--name在命令行上看到但 name在应用程序中的任何位置都没有被定义 的正常错误退出
三是“递归”的标志,这导致其他标记值被设置:--fromenv,--tryfromenv, --flagfile。这些将在下面更详细地描述。
--fromenv
--fromenv=foo,bar说从环境中读取foo和bar标志的值 。与此标志一致,您必须实际设置环境中的值,方法如下所示:
export FLAGS_foo = xxx; export FLAGS_bar = yyy #sh
setenv FLAGS_foo xxx; setenv FLAGS_bar yyy #tcsh
这相当于指定--foo=xxx, --bar=yyy在命令行。
请注意,--fromenv=foo如果foo在应用程序中某处未进行DEFINED,那么 这是一个致命的错误。(虽然你可以通过它来抑制这个错误--undefok=foo,就像任何其他标志一样。)
--fromenv=foo如果 FLAGS_foo实际上没有在环境中定义,那也是一个致命的错误。
--tryfromenv
--tryfromenv是完全一样的--fromenv,除非它不是一个致命的错误, --tryfromenv=foo如果FLAGS_foo没有在环境中实际定义。相反,在这种情况下, FLAGS_foo只保留应用程序中指定的默认值。
请注意,--tryfromenv=foo如果 foo在应用程序中某处未进行DEFINED 仍然是错误的。
--flagfile
--flagfile=f告诉commandlineflags模块读取文件f,并运行在该文件中找到的所有标志分配,就像在命令行上指定了这些标志一样。
在最简单的形式中,f应该只是一个标志分配列表,每行一个。不像在命令行中,等号分开它的参数一flagname是需要的flagfiles。示例标志文件,/tmp/myflags:
--nobig_menus
--languages =英语,法语
使用此标志文件,以下两行是等效的:
./myapp --foo --nobig_menus --languages = english,french --bar
./myapp --foo --flagfile = / tmp / myflags --bar
请注意,在标记文件中会静默抑制许多错误。特别是,无法识别的flagnames会被忽略,因为缺少必需值的标志(例如,刚才说的标志文件 --languages)。
标志文件的一般格式比上面简单的常见情况要复杂一些。它是:一系列文件名,每行一个,后跟一系列标志,每行一个,根据需要重复多次。标志文件中的文件名可以使用通配符(*和?),只有当前可执行文件的名称与其中一个文件名匹配时,才会处理位于文件名序列之后的标志序列。可以使用一系列标志而不是一系列文件名来启动标志文件; 如果存在这样的标志序列,则无论它是什么,这些标志都应用于当前可执行文件。
以a开头的行将#被忽略为注释。在标记文件中也会忽略前导空格,空行也是如此。
--flagfile 标志文件可以使用该标志来包含另一个标志文件。
标志始终按预期顺序处理。也就是说,处理从检查命令行上直接指定的标志开始。如果指定了标志文件,则处理其内容,然后继续处理来自命令行的剩余标志。
API
除了FLAGS_foo直接访问外,还可以通过API以编程方式访问标志。还可以访问有关标志的信息,例如其默认值和帮助字符串。A FlagSaver可以轻松修改标志,然后在以后自动撤消修改。最后,有一些不相关但有用的例程可以轻松访问argv外部main的部分,包括程序名(argv[0])。
有关这些例程的详细信息,以及其他有用的辅助方法,如gflags::SetUsageMessage()和 gflags::SetVersionString,见gflags.h。
杂项说明
如果您的应用程序包含以下代码:
#define STRIP_FLAG_HELP 1 //这必须在#include之前!
#include <gflags / gflags.h>
我们将从已编译的源中删除帮助消息。这可以稍微减小所得到的二进制文件的大小,并且出于安全原因也可能是有用的。
问题和功能请求
请在GitHub上报告任何有关其他功能的问题或想法。我们还想鼓励针对错误修复的拉取请求和新功能的实现。
Craig Silverstein,Andreas Schuh
Mon 2018年11月12日