C++ getopt 的替代方案





5.00/5 (11投票s)
命令行选项的解析器
引言
如今,命令行程序已不像过去那样流行。然而,有时,创建一个这样的程序比创建基于 GUI 的程序要容易得多。在 C 语言的世界里,自古以来,程序员就使用 getopt
函数来解析命令行参数。严格来说,getopt
并不是 C 语言的特性,它是 POSIX 的特性,因此,如果你使用的是 Microsoft Visual C++,它甚至不可用。
下面展示的代码是一个简单的 C++ 实现,用于替换传统的 C 语言 getopt
解析器,并提供了 POSIX 标准所需的大部分灵活性。
示例用法
假设我们需要解析如下命令行
testopt -y --params p1 p2 p3 -o 123 arg1 arg2 arg3
下面是能够实现此功能的程序
#include <mlib/options.h>
#include <iostream>
using namespace std;
using mlib::OptionsParser;
int main(int argc, char** argv)
{
OptParser parser{
"h|help \t show help message",
"y| \t boolean flag",
"n| \t another boolean flag",
"p+param parameters \t one or more parameters",
"o:option value \t optional value",
"*stuff things \t option with zero or more arguments"
};
这声明了解析器对象并定义了允许的选项。我们稍后将看到选项定义的具体语法。
int nonopt;
if (opt.parse (argc, argv, &nonopt) != 0)
{
cout << "Syntax error. Usage:" <<endl;
cout << opt.synopsis () << endl << "Where:" << opt.description () << endl;
exit (1);
}
opt.parse()
函数解析命令行参数。nonopt
被设置为第一个非选项参数的索引。在我们的示例命令行中,nonopt
将是 8
,即命令行中 arg1
的索引。
string par;
if (opt.getopt ("params", par))
{
cout << "params:" << par << endl;
}
}
如果命令行中存在某个选项,opt.getopt()
函数将返回一个非零值。如果该选项可以带有参数,则它们将作为 string
(在我们的示例中是 par
)返回,并由用户定义的字符(默认是竖线 '|
')分隔。
if (opt.hasopt ('y'))
cout << "Yes option set" << endl;
opt.hasopt()
函数返回 true
,如果命令行中存在该选项。
OptParser 理解的命令行语法
根据 POSIX 标准,命令行包含三个部分:命令名、选项及其参数以及操作数。POSIX 标准仅描述了由 -
(连字符)引导的短选项(单个字符),然而,通常也会有由 --
引导的长选项。跟在最后一个选项及其参数后面的参数称为操作数。如果其中一个参数是 --
,则选项处理将在此时停止,所有剩余的参数都将被视为操作数。选项可以带有一个或多个参数。如果一个选项有多个参数,则参数将在下一个选项开始时、遇到 --
参数时或到达命令行末尾时结束。短选项可以组合在一个连字符后面,前提是它们没有参数(除了最后一个可能)。例如,与其写
command -a -b -c
不如写
command -abc
选项可以重复。在这种情况下,如果选项带参数,所有参数都会被累积。例如,以下两行是等效的
command -a arg1 arg2 -b command -a arg1 -b -a arg2
通常使用一种类似 BNF 的符号来描述命令行语法,如下所示
command -a <arg_a> -b [<arg_b>...] -c|--clong <operand1> <operand2>
其中可选参数用方括号括起来,替代项用 '|
' 表示。可以重复出现一次或多次的参数用 '...'(省略号)表示。
定义选项
每个有效选项都由一个具有以下语法的描述符字符串描述
[<short>] <flag> [<long>] [<spaces><parameter>] [\t<description>]
其中
<short>
- 一个单字符,表示选项的短形式。<flag>
- 一个字符,指定选项后面可以有多少个参数- '|' - 无参数
- ':' - 一个必需参数
- '?' - 一个可选参数
- '+' - 一个或多个参数
- '*' - 零个或多个参数
<long>
- 一个string
,指定选项的长形式<parameter>
- 一个string
,在语法摘要中用作参数名<description>
- 一个string
,用于选项说明。参数名和描述由一个制表符\t
分隔。选项的长形式或短形式都可以省略。
OptParser API
前面已经提到了一些函数。
构造函数
OptParser () (1)
默认构造函数创建一个带有空有效选项列表的解析器对象。
OptParser (std::vector<const char*> &list) (2)
初始化解析器并设置有效选项列表。
OptParser (const char **list) (3)
初始化解析器并设置选项描述符列表。参数是一个以 NULL
指针结尾的 C string
列表。
OptParser (std::initializer_list<const char*> list) (4)
初始化解析器并设置有效选项列表。初始化列表不需要 NULL
终止符。请参见开头的示例代码。
成员函数
void add_option (const char* descr) (1)
向有效选项列表中添加一个新的选项描述符。
void set_options (std::vector <const char*> &list) (2)
设置有效选项列表。将删除所有先前的选项并添加新的选项。
int parse (int argc, const char* const* argv, int* stop=0) (3)
解析命令行参数。stop
是一个指向整数值的指针,如果该指针非 null
,则接收 argv
数组中第一个非选项参数的索引。如果没有非选项参数,则 stop == argc
。
如果成功,函数返回 0
。否则,它返回一个错误代码
- 1 = 未知选项
- 2 = 缺少必需参数
- 3 = 无效的多个选项
string
如果发生错误,stop
参数(如果非null
)是argv
数组中触发错误的参数的索引。
int getopt (char option, std::string& optarg, char sep='|') const (4)
int getopt (const std::string& option, std::string& optarg, char sep='|') const (5)
int getopt (char option, std::vector<std::string>& optarg) const (6)
int getopt (const std::string& option, std::vector<std::string>& optarg) const (7)
从命令行返回一个特定的选项。该函数返回命令行中选项的出现次数。对于 (4) 和 (5),optarg
参数接收一个 string
,其中包含所有选项的参数,并用 sep
字符分隔。 (6) 和 (7) 以 string
向量的形式返回选项参数。选项可以使用短形式(在 (4) 和 (6) 中)或长形式(在 (5) 和 (7) 中)指定。
bool hasopt (const std::string &option) const (8)
bool hasopt (char option) const (9)
检查命令行中是否存在某个选项。选项可以使用短形式(9)或长形式(8)指定。
bool next (std::string& opt, std::string& optarg, char sep='|') (10)
bool next (std::string& opt, std::vector<std::string>& optarg) (11)
返回命令行中的下一个选项。解析器维护一个内部迭代器,该迭代器在解析命令行时被初始化为第一个可用选项。每次调用 next
函数时,迭代器都会递增,函数将返回下一个选项及其参数。如果没有更多选项,函数将返回 false
。
形式 (10) 返回一个 string
,其中包含所有选项的参数,并用 sep
字符分隔。形式 (11) 以 string
向量的形式返回参数。
const std::string synopsis () const (12)
生成一个格式良好的语法 string
。对于前面显示的示例,语法 string
是
appname -h|--help -y -n -p|--param <parameters>... -o|--option <value> --stuff [things ...]
其中 appname
是可执行文件的实际名称。
const std::string description (size_t indent_size=2) const (13)
生成一个格式良好的描述 string
。对于前面显示的示例,描述 string
是
-h|--help show help message -y boolean flag -n another boolean flag -p|--param <parameters>... one or more parameters -o|--option <value> optional value --stuff [things ...] option with zero or more arguments
const std::string& appname () const (14)
返回程序名称。这是 argv[0]
的内容,去掉了目录路径和扩展名。
以防您好奇
- 如何处理带引号的字符串?它们不被处理。
OptParser
依赖操作系统来解析命令行参数。 - 如果参数被组合,为什么您返回选项出现的次数?因为它允许您执行更精细的操作。例如,如果应用程序有一个用于详细模式的选项 '
-v
',您可以通过重复该选项来增加详细程度。'-v
' 将是详细模式,'-vv
' 将更详细,'-vvv
' 将是极其详细,依此类推。 - 一些命令行解析器允许您使用等号分隔参数值(例如
-o=123
)。您的可以做到吗?不可以,首先,这种语法超出了 POSIX 规范。此外,它会引发有关参数引用的棘手问题。 OptParser
的效率如何?嗯,效率不高。为了满足其所有存储需求,OptParser
使用string
和string
向量。通常,选项解析是一次性活动,其对执行时间的影响很小。效率不是设计目标。
替代解决方案
还有其他提供类似功能的软件包。如果您想查看替代方案,可以看看
- Boost Program Options。像所有 Boost 的东西一样,它非常完整、复杂且庞大。
- LLVM CommandLine。对我来说,仅为了命令行解析而使用 LLVM 似乎有点大材小用。
- Taywee args
- args-parser
- tclap
我没有尝试过所有这些,但我很想听听其他人的意见。
历史
- 2023 年 1 月 2 日:初始版本