解析c++中的命令行参数?

如果程序被指定像这样运行,那么在c++中解析命令行参数的最佳方式是什么:

prog [-abc] [input [output]]

是否有某种方法可以在标准库中实现这一点,或者我需要自己编写代码?


相关:

401410 次浏览

GNU GetOpt

一个使用GetOpt的简单示例:

// C/C++ Libraries:
#include <string>
#include <iostream>
#include <unistd.h>


// Namespaces:
using namespace std;


int main(int argc, char** argv) {
int opt;
bool flagA = false;
bool flagB = false;


// Shut GetOpt error messages down (return '?'):
opterr = 0;


// Retrieve the options:
while ( (opt = getopt(argc, argv, "ab")) != -1 ) {  // for each option...
switch ( opt ) {
case 'a':
flagA = true;
break;
case 'b':
flagB = true;
break;
case '?':  // unknown option...
cerr << "Unknown option: '" << char(optopt) << "'!" << endl;
break;
}
}


// Debug:
cout << "flagA = " << flagA << endl;
cout << "flagB = " << flagB << endl;


return 0;
}

如果你有接受参数的选项,你也可以使用< em > optarg < / em >

在GNU C库中有这些工具,其中包括GetOpt

如果你正在使用Qt并且喜欢GetOpt接口,froglogic已经发布了一个不错的接口在这里

argstreamboost.program_option非常相似:它允许将变量绑定到选项等。但是,它不处理存储在配置文件中的选项。

并且有一个谷歌图书馆可用。

实际上,命令行解析已经“解决”了。随便选一个。

在c++中,答案通常在Boost中…

Boost。程序选择< / >

我建议你去图书馆。有经典和古老的getopt,我相信还有其他的。

如果你不想使用boost,我推荐小帮助类。

如果你只想自己处理命令行选项,简单的的方法是:

vector<string> args(argv + 1, argv + argc);

main()的顶部。这将把所有命令行参数复制到__abc1的向量中。然后你可以使用==轻松地比较字符串,而不是无休止的strcmp()调用。例如:

int main(int argc, char **argv) {
vector<string> args(argv + 1, argv + argc);
string infname, outfname;


// Loop over command-line args
// (Actually I usually use an ordinary integer loop variable and compare
// args[i] instead of *i -- don't tell anyone! ;)
for (auto i = args.begin(); i != args.end(); ++i) {
if (*i == "-h" || *i == "--help") {
cout << "Syntax: foomatic -i <infile> -o <outfile>" << endl;
return 0;
} else if (*i == "-i") {
infname = *++i;
} else if (*i == "-o") {
outfname = *++i;
}
}
}

[编辑:我意识到我正在复制argv[0],程序的名称,到args -已修复。]

有很多好的库可用。

增强计划选项是一个相当重量级的解决方案,因为将它添加到你的项目需要你构建boost,而且语法有点混乱(在我看来)。但是,它几乎可以做任何事情,包括让命令行选项覆盖配置文件中设置的选项。

SimpleOpt是一个相当全面但简单的命令行处理器。它是一个单一的文件,具有简单的结构,但只处理将命令行解析为选项,您必须进行所有的类型和范围检查。它适用于Windows和Unix,并且还附带了一个适用于Windows的glob版本。

getopt在Windows上可用。它与Unix机器上的相同,但它通常是一个GPL库。

您可以使用GNU GetOpt (LGPL)或各种c++端口之一,例如getoptpp (GPL)。

一个简单的例子使用GetOpt你想要的东西(Prog [-ab]输入)如下所示:

// C Libraries:
#include <string>
#include <iostream>
#include <unistd.h>


// Namespaces:
using namespace std;


int main(int argc, char** argv) {
int opt;
string input = "";
bool flagA = false;
bool flagB = false;


// Retrieve the (non-option) argument:
if ( (argc <= 1) || (argv[argc-1] == NULL) || (argv[argc-1][0] == '-') ) {  // there is NO input...
cerr << "No argument provided!" << endl;
//return 1;
}
else {  // there is an input...
input = argv[argc-1];
}


// Debug:
cout << "input = " << input << endl;


// Shut GetOpt error messages down (return '?'):
opterr = 0;


// Retrieve the options:
while ( (opt = getopt(argc, argv, "ab")) != -1 ) {  // for each option...
switch ( opt ) {
case 'a':
flagA = true;
break;
case 'b':
flagB = true;
break;
case '?':  // unknown option...
cerr << "Unknown option: '" << char(optopt) << "'!" << endl;
break;
}
}


// Debug:
cout << "flagA = " << flagA << endl;
cout << "flagB = " << flagB << endl;


return 0;
}

如果你可以使用boost库,我推荐boost::program_options。

在STL和常规的c++ /C运行时库中都没有特定的东西。

boost::program_options和GNU getopt的建议是很好的。

然而,对于简单的命令行选项,我倾向于使用std::find

例如,在-f命令行参数之后读取文件名。您还可以检测是否传递了一个单字选项,如-h,以寻求帮助。

#include <algorithm>


char* getCmdOption(char ** begin, char ** end, const std::string & option)
{
char ** itr = std::find(begin, end, option);
if (itr != end && ++itr != end)
{
return *itr;
}
return 0;
}


bool cmdOptionExists(char** begin, char** end, const std::string& option)
{
return std::find(begin, end, option) != end;
}


int main(int argc, char * argv[])
{
if(cmdOptionExists(argv, argv+argc, "-h"))
{
// Do stuff
}


char * filename = getCmdOption(argv, argv + argc, "-f");


if (filename)
{
// Do interesting things
// ...
}


return 0;
}

使用这种方法需要注意的一点是,必须使用std::strings作为std::find的值,否则将对指针值执行相等性检查。


我希望它是可以编辑这个回应,而不是添加一个新的,因为这是基于原始的答案。我稍微重写了函数,并将它们封装在一个类中,下面是代码。我认为这样使用也很实用:

class InputParser{
public:
InputParser (int &argc, char **argv){
for (int i=1; i < argc; ++i)
this->tokens.push_back(std::string(argv[i]));
}
/// @author iain
const std::string& getCmdOption(const std::string &option) const{
std::vector<std::string>::const_iterator itr;
itr =  std::find(this->tokens.begin(), this->tokens.end(), option);
if (itr != this->tokens.end() && ++itr != this->tokens.end()){
return *itr;
}
static const std::string empty_string("");
return empty_string;
}
/// @author iain
bool cmdOptionExists(const std::string &option) const{
return std::find(this->tokens.begin(), this->tokens.end(), option)
!= this->tokens.end();
}
private:
std::vector <std::string> tokens;
};


int main(int argc, char **argv){
InputParser input(argc, argv);
if(input.cmdOptionExists("-h")){
// Do stuff
}
const std::string &filename = input.getCmdOption("-f");
if (!filename.empty()){
// Do interesting things ...
}
return 0;
}

Boost::程序选项试试。它允许您读取和解析命令行以及配置文件。

我喜欢C的getopt(),但是我老了。: -)

谷歌的gflags

尝试CLPP库。它是用于命令行参数解析的简单而灵活的库。仅头部和跨平台。仅使用ISO c++和Boost c++库。恕我直言,这比Boost.Program_options简单。

库:http://sourceforge.net/projects/clp-parser

2010年10月26日-新发布2.0rc。修正了许多bug,完整的源代码重构、文档、示例和注释都得到了修正。

尝试CLPP库。它是用于命令行参数解析的简单而灵活的库。仅头部和跨平台。仅使用ISO c++和Boost c++库。恕我直言,这比Boost.Program_options简单。

库:http://sourceforge.net/projects/clp-parser/

2010年10月26日-新发布2.0rc。修正了许多bug,完整的源代码重构、文档、示例和注释都得到了修正。

这是我最喜欢的执行命令行的方式,特别是,但绝对不是只有在效率是一个问题。这可能看起来有点过分,但我认为这种过分有一些缺点。

使用gperf进行高效的C/ c++命令行处理

缺点:

  • 您必须首先运行一个单独的工具来生成C/ c++哈希表的代码
  • 不支持特定的命令行接口。例如,posix简写系统“-xyz”用一个破折号声明多个选项是很难实现的。

优点:

  • 命令行选项与c++代码分开存储(在单独的配置文件中,不需要在运行时读取,只需要在编译时读取)。
  • 您的代码中只有一个开关(打开枚举值)来确定您拥有哪个选项
  • 效率是O(n),其中n是命令行上的选项数量,而可能选项的数量是不相关的。最慢的部分可能是switch的实现(有时编译器倾向于像else块一样实现它们,降低了它们的效率,尽管如果你选择连续的值,这是不太可能的,参见:这篇文章是关于开关效率的)
  • 分配用于存储关键字的内存恰好足够关键字集,不能更大。
  • 也适用于C语言

使用像eclipse这样的IDE,您可能可以自动化运行gperf的过程,因此您惟一需要做的就是在配置文件和switch语句中添加一个选项,然后按build…

我使用了一个批处理文件来运行gperf,并做了一些清理,并使用sed添加了包含保护(在gperf生成的.hpp文件上)…

所以,在你的软件中有非常简洁干净的代码和一个自动生成的哈希表文件,你真的不需要手动更改。我怀疑boost::program_options即使没有效率作为优先级,实际上也能打败它。

你可能想要使用一个外部库。有许多可供选择。

Boost有一个非常丰富的功能库增强计划选项

在过去的几年里,我个人最喜欢的是TCLAP——纯粹的模板化,因此没有库或链接,自动生成“——帮助”和其他好东西。请参阅文档中的最简单的例子

如果这是linux/unix,那么使用的标准是gnu getopt

http://www.gnu.org/s/libc/manual/html_node/Getopt.html

尝试CLPP库。它是用于命令行参数解析的简单而灵活的库。仅头部和跨平台。仅使用ISO c++和Boost c++库。恕我直言,这比Boost.Program_options简单。

库:http://sourceforge.net/projects/clp-parser

2010年10月26日-新发布2.0rc。修正了许多bug,完整的源代码重构、文档、示例和注释都得到了修正。

我已经使用GetPot的一些项目: http://getpot.sourceforge.net/ < / p >

主要特点:所有东西都在一个头文件中,没有构建的麻烦。只要把它保存在你的机器上的某个地方,然后&;#include&;它在你的文件中包含main()

最近没有更新,但它有很好的文档记录,工作得很好。

我发现使用ezOptionParser更容易。它也是一个单头文件,只依赖于STL,适用于Windows和Linux(很可能也适用于其他平台),由于这些示例,它没有学习曲线,具有其他库没有的特性(如带有注释的文件导入/导出、带有分隔符的任意选项名称、自动使用格式等),并且是LGPL许可的。

for (int i = 1; i < argc; i++) {


if (strcmp(argv[i],"-i")==0) {
filename = argv[i+1];
printf("filename: %s",filename);
} else if (strcmp(argv[i],"-c")==0) {
convergence = atoi(argv[i + 1]);
printf("\nconvergence: %d",convergence);
} else if (strcmp(argv[i],"-a")==0) {
accuracy = atoi(argv[i + 1]);
printf("\naccuracy:%d",accuracy);
} else if (strcmp(argv[i],"-t")==0) {
targetBitRate = atof(argv[i + 1]);
printf("\ntargetBitRate:%f",targetBitRate);
} else if (strcmp(argv[i],"-f")==0) {
frameRate = atoi(argv[i + 1]);
printf("\nframeRate:%d",frameRate);
}


}

AnyOption是一个c++类,用于方便地解析复杂的命令行选项。它还以选项值对格式解析来自rsourcefile的选项。

AnyOption实现了传统的POSIX风格的字符选项(-n)以及较新的GNU风格的长选项(——name)。或者您可以通过要求忽略POSIX样式选项来使用更简单的长选项版本(-name)。

我可以建议模板化的c++命令行解析器库(一些GitHub上的叉子是可用的),API是非常直接的(引用自网站):

这个库完全在头文件中实现,这使得它很容易 与其他软件一起使用和分发。它是MIT授权的

这是手册中的一个例子,为了简单起见,这里有颜色:

#include <string>
#include <iostream>
#include <algorithm>
#include <tclap/CmdLine.h>


int main(int argc, char** argv)
{


// Wrap everything in a try block.  Do this every time,
// because exceptions will be thrown for problems.
try {


// Define the command line object, and insert a message
// that describes the program. The "Command description message"
// is printed last in the help text. The second argument is the
// delimiter (usually space) and the last one is the version number.
// The CmdLine object parses the argv array based on the Arg objects
// that it contains.
TCLAP::CmdLine cmd("Command description message", ' ', "0.9");


// Define a value argument and add it to the command line.
// A value arg defines a flag and a type of value that it expects,
// such as "-n Bishop".
TCLAP::ValueArg<std::string> nameArg("n","name","Name to print",true,"homer","string");


// Add the argument nameArg to the CmdLine object. The CmdLine object
// uses this Arg to parse the command line.
cmd.add( nameArg );


// Define a switch and add it to the command line.
// A switch arg is a boolean argument and only defines a flag that
// indicates true or false.  In this example the SwitchArg adds itself
// to the CmdLine object as part of the constructor.  This eliminates
// the need to call the cmd.add() method.  All args have support in
// their constructors to add themselves directly to the CmdLine object.
// It doesn't matter which idiom you choose, they accomplish the same thing.
TCLAP::SwitchArg reverseSwitch("r","reverse","Print name backwards", cmd, false);


// Parse the argv array.
cmd.parse( argc, argv );


// Get the value parsed by each arg.
std::string name = nameArg.getValue();
bool reverseName = reverseSwitch.getValue();


// Do what you intend.
if ( reverseName )
{
std::reverse(name.begin(),name.end());
std::cout << "My name (spelled backwards) is: " << name << std::endl;
}
else
std::cout << "My name is: " << name << std::endl;




} catch (TCLAP::ArgException &e)  // catch any exceptions
{ std::cerr << "error: " << e.error() << " for arg " << e.argId() << std::endl; }
}

还有另一种选择是精益平均c++选项解析器:

http://optionparser.sourceforge.net

它是一个仅头文件的库(实际上只是一个头文件),与所有其他建议不同 也是独立的,即它没有任何依赖关系。特别是,它不依赖于STL。它甚至不使用异常或任何其他需要库支持的东西。这意味着它可以与普通C或其他语言链接,而无需引入“外部”库 像boost::program_options一样,它的API提供了方便的直接访问选项, 也就是说,你可以写这样的代码

if (options[HELP])…;

而且

int VERBOSE = options[VERBOSE].count();

然而,与boost::program_options不同的是,这只是使用一个带有(用户提供的)enum索引的数组。这提供了没有权重的关联容器的便利。

它有良好的文档记录,并具有公司友好的许可证(MIT)。

tlmc++ OP包含了一个很好的格式化程序,用于使用消息 行换行和列对齐在本地化程序时非常有用,因为它可以确保即使在具有较长消息的语言中输出也会很好。它还省去了手动格式化80列的麻烦。< / p >

TCLAP是一个非常好的轻量级设计,易于使用: http://tclap.sourceforge.net/ < / p >

如果可以的话,我想自吹自擂,我还想建议看看我写的选项解析库:放下

  • 它是一个C库(如果需要,还带有c++包装器)。
  • 它是轻量级的。
  • 它是可扩展的(自定义参数类型可以很容易地添加,并且与内置参数类型具有相同的基础)。
  • 它应该是非常可移植的(它是用标准C编写的),没有依赖关系(除了C标准库)。
  • 它有一个非常无限制的许可证(zlib/libpng)。

它提供了许多其他功能没有的一个功能是覆盖先前选项的能力。例如,如果你有一个shell别名:

alias bar="foo --flag1 --flag2 --flag3"

并且你想要使用bar但禁用了__abc1,它允许你这样做:

bar --flag1=0

Qt 5.2自带命令行解析器API

小例子:

#include <QCoreApplication>
#include <QCommandLineParser>
#include <QDebug>


int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
app.setApplicationName("ToolX");
app.setApplicationVersion("1.2");


QCommandLineParser parser;
parser.setApplicationDescription("Tool for doing X.");
parser.addHelpOption();
parser.addVersionOption();
parser.addPositionalArgument("infile",
QCoreApplication::translate("main", "Input file."));


QCommandLineOption verbose_opt("+",
QCoreApplication::translate("main", "be verbose"));
parser.addOption(verbose_opt);


QCommandLineOption out_opt(QStringList() << "o" << "output",
QCoreApplication::translate("main", "Output file."),
QCoreApplication::translate("main", "filename"), // value name
QCoreApplication::translate("main", "out")   // default value
);
parser.addOption(out_opt);


// exits on error
parser.process(app);


const QStringList args = parser.positionalArguments();


qDebug() << "Input files: " << args
<< ", verbose: " << parser.isSet(verbose_opt)
<< ", output: " << parser.value(out_opt)
<< '\n';
return 0;
}

示例输出

自动生成的帮助界面:

$ ./qtopt -h
Usage: ./qtopt [options] infile
Tool for doing X.


Options:
-h, --help               Displays this help.
-v, --version            Displays version information.
-+                       be verbose
-o, --output   Output file.


Arguments:
infile                   Input file.

自动生成版本输出:

$ ./qtopt -v
ToolX 1.2

一些真实的电话:

$ ./qtopt b1 -+ -o tmp blah.foo
Input files:  ("b1", "blah.foo") , verbose:  true , output:  "tmp"
$ ./qtopt
Input files:  () , verbose:  false , output:  "out"

解析错误:

$ ./qtopt --hlp
Unknown option 'hlp'.
$ echo $?
1

结论

如果您的程序已经使用了Qt(>= 5.2)库,那么它的命令行解析API足以方便地完成工作。

注意,内置Qt选项在选项解析器运行之前会被QApplication使用。

你可以试试我的小选项头(166 loc很容易被黑客攻击)options.hpp。它是一个单头实现,应该按您的要求执行。它还会自动打印帮助页面。

我认为GNU GetOpt并不是马上就可以使用的。

Qt和Boost可能是一种解决方案,但您需要下载并编译大量代码。

所以我自己实现了一个解析器,它产生一个std::map<std::string, std::string>的参数。

例如,调用:

 ./myProgram -v -p 1234

地图将是:

 ["-v"][""]
["-p"]["1234"]

用法是:

int main(int argc, char *argv[]) {
MainOptions mo(argc, argv);
MainOptions::Option* opt = mo.getParamFromKey("-p");
const string type = opt ? (*opt).second : "";
cout << type << endl; /* Prints 1234 */
/* Your check code */
}

MainOptions.h

#ifndef MAINOPTIONS_H_
#define MAINOPTIONS_H_


#include <map>
#include <string>


class MainOptions {
public:
typedef std::pair<std::string, std::string> Option;
MainOptions(int argc, char *argv[]);
virtual ~MainOptions();
std::string getAppName() const;
bool hasKey(const std::string&) const;
Option* getParamFromKey(const std::string&) const;
void printOptions() const;
private:
typedef std::map<std::string, std::string> Options;
void parse();
const char* const *begin() const;
const char* const *end() const;
const char* const *last() const;
Options options_;
int argc_;
char** argv_;
std::string appName_;
};

MainOptions.cpp

#include "MainOptions.h"


#include <iostream>


using namespace std;


MainOptions::MainOptions(int argc, char* argv[]) :
argc_(argc),
argv_(argv) {
appName_ = argv_[0];
this->parse();
}


MainOptions::~MainOptions() {
}


std::string MainOptions::getAppName() const {
return appName_;
}


void MainOptions::parse() {
typedef pair<string, string> Option;
Option* option = new pair<string, string>();
for (const char* const * i = this->begin() + 1; i != this->end(); i++) {
const string p = *i;
if (option->first == "" && p[0] == '-') {
option->first = p;
if (i == this->last()) {
options_.insert(Option(option->first, option->second));
}
continue;
} else if (option->first != "" && p[0] == '-') {
option->second = "null"; /* or leave empty? */
options_.insert(Option(option->first, option->second));
option->first = p;
option->second = "";
if (i == this->last()) {
options_.insert(Option(option->first, option->second));
}
continue;
} else if (option->first != "") {
option->second = p;
options_.insert(Option(option->first, option->second));
option->first = "";
option->second = "";
continue;
}
}
}


void MainOptions::printOptions() const {
std::map<std::string, std::string>::const_iterator m = options_.begin();
int i = 0;
if (options_.empty()) {
cout << "No parameters\n";
}
for (; m != options_.end(); m++, ++i) {
cout << "Parameter [" << i << "] [" << (*m).first << " " << (*m).second
<< "]\n";
}
}


const char* const *MainOptions::begin() const {
return argv_;
}


const char* const *MainOptions::end() const {
return argv_ + argc_;
}


const char* const *MainOptions::last() const {
return argv_ + argc_ - 1;
}


bool MainOptions::hasKey(const std::string& key) const {
return options_.find(key) != options_.end();
}


MainOptions::Option* MainOptions::getParamFromKey(
const std::string& key) const {
const Options::const_iterator i = options_.find(key);
MainOptions::Option* o = 0;
if (i != options_.end()) {
o = new MainOptions::Option((*i).first, (*i).second);
}
return o;
}

你的C/ c++程序总是有一个主函数。它是这样的:

    int main(int argc, char**argv) {
...
}

这里argc是一些命令行参数,已经传递给你的程序,argv是一个包含这些参数的字符串数组。因此,命令行参数由调用方进程分隔开(不像在windows中那样是一行)。

现在你需要整理它们:

  • 命令名总是第一个参数(索引0)。
  • 选项只是指定程序应如何工作的特殊参数。按照惯例,它们从-号开始。通常-对于一个字母的选项和-对于任何更长。所以在你的任务中“选项”都是参数,从-开始,而不是第0个。
  • 参数。只是所有其他不是程序名或选项的参数。

命令基本上是一个字符串。一般来说,它可以分为两部分——命令的name和命令的arguments

例子:

ls

用于列出目录的内容:

user@computer:~$ ls
Documents Pictures Videos ...

上面的ls是在用户的home文件夹中执行的。在这里,要列出哪个文件夹的参数隐式添加到命令中。我们可以显式地传递一些参数:

user@computer:~$ ls Picture
image1.jpg image2.jpg ...

这里我已经显式地告诉ls我想要查看哪个文件夹的内容。我们可以使用另一个参数(例如l)来列出每个文件和文件夹的详细信息,例如访问权限、大小等:

user@computer:~$ ls Pictures
-rw-r--r-- 1 user user   215867 Oct 12  2014 image1.jpg
-rw-r--r-- 1 user user   268800 Jul 31  2014 image2.jpg
...

哦,大小看起来真的很奇怪(215867268800)。让我们为人性化输出添加h标志:

user@computer:~$ ls -l -h Pictures
-rw-r--r-- 1 user user  211K Oct 12  2014 image1.jpg
-rw-r--r-- 1 user user  263K Jul 31  2014 image2.jpg
...

一些命令允许它们的参数组合(在上面的情况下,我们不妨编写ls -lh,我们将得到相同的输出),使用short(通常是一个字母,但有时更多;缩写)或长名称(对于ls,我们有-a--all用于列出所有文件,包括隐藏的文件,其中--all-a的长名称)等。有一些命令的论证的顺序非常重要,但也有一些命令的参数的顺序根本不重要

例如,如果我使用ls -lhls -hl并不重要,但是在mv(移动/重命名文件)的情况下,你对最后两个参数mv [OPTIONS] SOURCE DESTINATION的灵活性较小。

为了掌握命令及其参数,你可以使用man(例如:man ls)或info(例如:info ls)。

在包括C/ c++在内的许多语言中,您都有一种解析用户附加到可执行文件(命令)调用的命令行参数的方法。也有很多库可以完成这个任务,因为它的核心实际上并不容易正确地完成它,同时提供大量的参数及其种类:

  • getopt
  • argp_parse
  • gflags
  • ...

每个C/ c++应用程序都有所谓的入口点,基本上就是你的代码开始的地方——main函数:

int main (int argc, char *argv[]) { // When you launch your application the first line of code that is ran is this one - entry point
// Some code here
return 0; // Exit code of the application - exit point
}

无论你是否使用库(就像我上面提到的其中一个;但这在你的情况下显然是不允许的;))或自己做,你的main函数有两个参数:

  • argc -表示参数的数量
  • argv -指向字符串数组的指针(你也可以看到char** argv,它基本相同,但更难使用)。

注意: main实际上还有第三个参数char *envp[],它允许将环境变量传递给你的命令,但这是一个更高级的东西,我真的不认为在你的情况下需要它。

命令行参数的处理由两部分组成:

  1. 分词 -这是每个参数得到意义的部分。它是将参数列表分解为有意义的元素(标记)的过程。在ls -l的情况下,l不仅是一个有效字符,而且本身也是一个标记,因为它代表了一个完整的有效参数。

下面是一个示例,如何输出参数的数量和(有效性未检查)字符,这些字符可能是参数,也可能不是参数:

#include <iostream>
using std::cout;
using std::endl;


int main (int argc, char *argv[]) {
cout << "Arguments' count=%d" << argc << endl;


// First argument is ALWAYS the command itself
cout << "Command: " << argv[0] << endl;


// For additional arguments we start from argv[1] and continue (if any)
for (int i = 1; i < argc; i++) {
cout << "arg[" << i << "]: " << argv[i] << endl;
}


cout << endl;
return 0;
}
  1. 解析 -在获取令牌(参数及其值)后,您需要检查您的命令是否支持这些令牌。例如:

    user@computer:~$ ls -y
    

    将返回

    ls: invalid option -- 'y'
    Try 'ls --help' for more information.
    

    这是因为解析失败了。为什么?因为y(和-y分别;注意,---:等不是必需的,取决于参数的解析,不管你是否想要这些东西;在Unix/Linux系统中,这是一种约定,但你不会绑定到它)是ls命令的未知参数

对于每个参数(如果成功识别),您将在应用程序中触发某种更改。例如,您可以使用if-else来检查某个参数是否有效,以及它所做的事情,然后在执行其余代码时更改您希望该参数更改的任何内容。你可以使用旧的C风格或c++风格:

* `if (strcmp(argv[1], "x") == 0) { ... }` - compare the pointer value
* `if (std::string(argv[1]) == "x") { ... }` - convert to string and then compare

我实际上喜欢(当不使用库时)将argv转换为字符串的std::vector,如下所示:

std::vector<std::string> args(argv, argv+argc);
for (size_t i = 1; i < args.size(); ++i) {
if (args[i] == "x") {
// Handle x
}
else if (args[i] == "y") {
// Handle y
}
// ...
}

std::vector<std::string> args(argv, argv+argc);部分只是一种更简单的c++方式来处理字符串数组,因为char *是一个C风格的字符串(char *argv[]是这样的字符串数组),可以很容易地转换为std::string的c++字符串。然后,我们可以将所有转换后的字符串添加到一个向量中,方法是给出argv的起始地址,然后指向它的最后一个地址,即argv + argc(我们将argc number的字符串添加到argv的基址,基本上指向数组的最后一个地址)。

在上面的for循环中,你可以看到我检查(使用简单的if-else)某个参数是否可用,如果是,则相应地处理它。提醒一句:通过使用这样一个循环来改变参数的顺序不重要。正如我在开头提到的,一些命令实际上对部分或全部参数有严格的顺序。你可以通过手动调用每个args的内容来处理这个问题(如果你使用初始char* argv[]而不是向量解决方案,则可以调用argv):

// No for loop!
if (args[1] == "x") {
// Handle x
}
else if (args[2] == "y") {
// Handle y
}
// ...

这确保在位置1中只有x将被期望等等。这样做的问题是,你可能会因为索引越界而伤了自己的腿,所以你必须确保你的索引保持在argc设置的范围内:

if (argc > 1 && argc <= 3) {
if (args[1] == "x") {
// Handle x
}
else if (args[2] == "y") {
// Handle y
}
}

上面的例子确保你的内容在索引12处,但没有超过。

最后但并非最不重要的是,每个参数的处理完全取决于你。你可以使用布尔标志,当检测到某个参数时设置(例如:if (args[i] == "x") { xFound = true; }和稍后在你的代码中基于bool xFound及其值做一些事情),数值类型,如果参数是一个数字或由数字和参数的名称组成(例如:mycommand -x=4有一个参数-x=4,你可以另外解析为x4,最后是x的值)等等。根据手头的任务,您可以疯狂地为您的命令行参数添加大量的复杂性。

希望这能有所帮助。如果有什么不清楚或者你需要更多的例子,请告诉我。

根据我的评论和rbaleksandar的回答,在C中传递给任何程序的参数都是字符串值。你会得到参数计算 (argc),它提供给你参数索引从零开始的,以当前正在运行的程序的名称开头(总是argv[0])。这将把1 - argc之间的所有参数保留为用户为程序提供的参数。每个都将是包含在参数向量中的字符串(你会看到argv[0]0被写为char *argv[],或等效地作为函数参数char **argv),每个字符串argv[1]argv[argc-1]都是可用的,你只需要测试哪个参数是哪个参数。

这将允许你分离它们,并使它们作为命令 (cmd), 选项 (opt)和最后的论点 (arg)到你的cmd

现在值得注意的是,你的shell (bash等)的规则适用于传递给你的程序的参数,分词路径名变量扩展在你的代码获得参数之前应用。因此,你必须考虑在你的任何参数周围是否需要或更常见的双引号,以防止正常的shell分裂,否则将适用(例如,ls -al my file.txt将导致4用户提供的参数到你的代码中,而ls -al "my file.txt"ls -al my\ file.txt将导致你期望的3

把所有这些放在一起,您的简短解析可以像下面这样完成。(你也可以随心所欲,使用switch来代替嵌套的__abc1,等等……)

#include <stdio.h>


int main (int argc, char **argv) {


char *cmd = NULL,   /* here, since you are using the arguments  */
*opt = NULL,   /* themselves, you can simply use a pointer */
*arg = NULL;   /* or the argument itself without a copy    */


/* looping using the acutal argument index & vector */
for (int i = 1; i < argc; i++) {
if (*argv[i] != '-') {      /* checking if the 1st char is - */
if (!cmd)               /* cmd is currently NULL, and    */
cmd = argv[i];      /* no '-' it's going to be cmd   */
else                    /* otherwise, cmd has value, so  */
arg = argv[i];       /* the value will be opt        */
}
else                /* here the value has a leading '-', so  */
opt = argv[i];  /* it will be the option */
}


printf ("\n cmd : %s\n opt : %s\n arg : %s\n\n",
cmd, opt, arg);


return 0;
}

使用/输出示例

如果你运行代码,你会发现它为参数提供了分离,并提供了单独的指针,以方便它们的使用:

$ ./bin/parse_cmd ls -la ./cs3000


cmd : ls
opt : -la
arg : ./cs3000

(需要注意的是,如果你的任务是构建一个命令字符串,你需要复制多个值,比如optarg,那么你就不能再简单地使用指针,而需要创建存储空间,要么通过简单地声明数组而不是指针开始,要么你可以根据需要动态分配存储空间,例如malloccalloc和/或realloc。然后,您将有可用的存储空间来复制和连接其中的值。)

如果这是你的挑战,那么在所有这些答案之间,你应该知道如何处理你的问题。如果你必须更进一步,实际上让你的程序用optarg执行cmd,那么你会想要查看fork来生成一个半独立的进程,在这个进程中你会用类似于execvexecvp的东西执行你的cmd optarg。祝你好运,如果你有进一步的问题,请发表评论。

我在windows/mingw下使用getopt():

while ((c = getopt(myargc, myargv, "vp:d:rcx")) != -1) {
switch (c) {
case 'v': // print version
printf("%s Version %s\n", myargv[0], VERSION);
exit(0);
break;
case 'p': // change local port to listen to
strncpy(g_portnum, optarg, 10);
break;
...

它太大了,不可能包含在Stack Overflow回答中,但我创建了一个用于声明式定义命令行的库。它利用了c++ 14通过给每个成员变量赋初始值来构建类构造函数的能力。

这个库基本上是一个基类。要定义命令语法,需要声明一个派生自该语法的结构。下面是一个例子:

struct MyCommandLine : public core::CommandLine {
Argument<std::string> m_verb{this, "program", "program.exe",
"this is what my program does"};
Option<bool> m_help{this, "help", false,
"displays information about the command line"};
Alias<bool> alias_help{this, '?', &m_help};
Option<bool> m_demo{this, "demo", false,
"runs my program in demonstration mode"};
Option<bool> m_maximize{this, "maximize", false,
"opens the main window maximized"};
Option<int> m_loops{this, "loops", 1,
"specifies the number of times to repeat"};
EnumOption<int> m_size{this, "size", 3,
{ {"s", 1},
{"small", 1},
{"m", 3},
{"med", 3},
{"medium", 3},
{"l", 5},
{"large", 5} } };
BeginOptionalArguments here{this};
Argument<std::string> m_file{this, "file-name", "",
"name of an existing file to open"};
} cl;

ArgumentOptionAlias类模板是在CommandLine基类的范围内声明的,你可以为自己的类型专门化它们。每个参数都包含this指针、选项名称、默认值和用于打印命令概要/用法的描述。

我仍然在寻找消除所有this指针的需要,但我还没有找到一种不引入宏的方法来做到这一点。这些指针允许每个成员向驱动解析的基类中的表注册自己。

一旦你有了一个实例,就会有几个方法重载来解析字符串或__abc0风格的参数向量的输入。解析器同时处理windows风格和unix风格的选项语法。

if (!cl.Parse(argc, argv)) {
std::string message;
for (const auto &error : cl.GetErrors()) {
message += error + "\n";
}
std::cerr << message;
exit(EXIT_FAILURE);
}

一旦它被解析,你可以使用operator()访问任何选项的值:

if (cl.m_help()) { std::cout << cl.GetUsage(); }
for (int i = 0; i < cl.m_loops(); ++i) { ... }

整个库只有大约300行(不包括测试)。实例有点臃肿,因为解析表是实例(而不是类)的一部分。但是每个程序通常只需要一个实例,而且这种纯声明性方法的便利性非常强大,可以通过解析新输入简单地重置实例。

一个简单的解决方案是将argv放入std::map中,以便于查找:

map<string, string> argvToMap(int argc, char * argv[])
{
map<string, string> args;


for(int i=1; i<argc; i++) {
if (argv[i][0] == '-') {
const string key = argv[i];
string value = "";
if (i+1 < argc && argv[i+1][0] != '-') {
value = string(argv[i+1]);
i++;
}


args[key] = value;
}
}


return args;
}

使用示例:

#include <map>
#include <string>
#include <iostream>


using namespace std;


map<string, string> argvToMap(int argc, char * argv[])
{
map<string, string> args;


for(int i=1; i<argc; i++) {
if (argv[i][0] == '-') {
const string key = argv[i];
string value = "";
if (i+1 < argc && argv[i+1][0] != '-') {
value = string(argv[i+1]);
i++;
}


args[key] = value;
}
}


return args;
}


void printUsage()
{
cout << "simple_args: A sample program for simple arg parsing\n"
"\n"
"Example usage:\n"
"    ./simple_args --print-all --option 1 --flag 2\n";
}


int main(int argc, char * argv[])
{
auto args = argvToMap(argc, argv);


if (args.count("-h") || args.count("--help")) {
printUsage();
}
else if (args.count("--print-all")) {
for (auto const & pair: args)
cout << "{" << pair.first << ": " << pair.second << "}\n";
}


return 0;
}

输出:

$ ./simple_args --print-all --option 1 --flag "hello world"
{--flag: hello world}
{--option: 1}
{--print-all: }

这种方法肯定有很大的局限性,但我发现它很好地平衡了简单性和实用性。