如果程序被指定像这样运行,那么在c++中解析命令行参数的最佳方式是什么:
prog [-abc] [input [output]]
是否有某种方法可以在标准库中实现这一点,或者我需要自己编写代码?
相关:
在unicode c++应用程序中解析命令行参数
如果程序被指定像这样运行,那么在c++中解析命令行参数的最佳方式是什么:
prog [-abc] [input [output]]
是否有某种方法可以在标准库中实现这一点,或者我需要自己编写代码?
相关:
在unicode c++应用程序中解析命令行参数
当前回答
我可以建议使用模板化的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; }
}
其他回答
我认为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;
}
命令基本上是一个字符串。通常它可以分为两部分——命令的名称和命令的参数。
例子:
ls
用于列出目录的内容:
user@computer:~$ ls
Documents Pictures Videos ...
上面的ls是在用户的主文件夹中执行的。在这里,要列出哪个文件夹的参数隐式添加到命令中。我们可以显式地传递一些参数:
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
...
哦,尺寸看起来很奇怪(215867,268800)。让我们为人性化输出添加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 -lh或ls -hl并不重要,但在mv(移动/重命名文件)的情况下,你的最后2个参数mv [OPTIONS] SOURCE DESTINATION的灵活性较低。
为了掌握命令及其参数,可以使用man(例如:man ls)或info(例如:info ls)。
在包括C/ c++在内的许多语言中,您都有一种解析用户附加到可执行文件(命令)调用的命令行参数的方法。也有很多库可以完成这个任务,因为它的核心实际上并不容易正确地完成它,同时提供大量的参数及其种类:
getopt argp_parse gflags ...
每个C/ c++应用程序都有所谓的入口点,基本上就是你的代码开始的地方——主函数:
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
}
无论你是否使用库(就像我上面提到的其中一个;但这显然是不允许在你的情况下;))或自己做,你的主函数有两个参数:
Argc -表示参数的个数 Argv -一个指向字符串数组的指针(你也可以看到char** Argv,它基本相同,但更难使用)。
注意:main实际上还有第三个参数char *envp[],它允许将环境变量传递给你的命令,但这是一个更高级的东西,我真的不认为在你的情况下需要它。
命令行参数的处理由两部分组成:
标记化-这是每个参数获得含义的部分。它是将参数列表分解为有意义的元素(标记)的过程。在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;
}
Parsing - after acquiring the tokens (arguments and their values) you need to check if your command supports these. For example: user@computer:~$ ls -y will return ls: invalid option -- 'y' Try 'ls --help' for more information. This is because the parsing has failed. Why? Because y (and -y respectively; note that -, --, : etc. is not required and its up to the parsing of the arguments whether you want that stuff there or not; in Unix/Linux systems this is a sort of a convention but you are not bind to it) is an unknown argument for the ls command.
对于每个参数(如果成功识别),您将在应用程序中触发某种更改。例如,您可以使用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
}
// ...
}
The std::vector<std::string> args(argv, argv+argc); part is just an easier C++-ish way to handle the array of strings since char * is a C-style string (with char *argv[] being an array of such strings) which can easily be converted to a C++ string that is std::string. Then we can add all converted strings to a vector by giving the starting address of argv and then also pointing to its last address namely argv + argc (we add argc number of string to the base address of argv which is basically pointing at the last address of our array).
Inside the for loop above you can see that I check (using simple if-else) if a certain argument is available and if yes then handle it accordingly. A word of caution: by using such a loop the order of the arguments doesn't matter. As I've mentioned at the beginning some commands actually have a strict order for some or all of their arguments. You can handle this in a different way by manually calling the content of each args (or argv if you use the initial char* argv[] and not the vector solution):
// 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
}
}
上面的示例确保在索引1和索引2处有内容,但没有超出索引1和索引2的内容。
Last but not least the handling of each argument is a thing that is totally up to you. You can use boolean flags that are set when a certain argument is detected (example: if (args[i] == "x") { xFound = true; } and later on in your code do something based on the bool xFound and its value), numerical types if the argument is a number OR consists of number along with the argument's name (example: mycommand -x=4 has an argument -x=4 which you can additionally parse as x and 4 the last being the value of x) etc. Based on the task at hand you can go crazy and add an insane amount of complexity to your command line arguments.
希望这能有所帮助。如果有什么不清楚或者你需要更多的例子,请告诉我。
你可能想要使用一个外部库。有很多选择。
Boost有一个非常丰富的功能(像往常一样)库Boost程序选项。
过去几年我个人最喜欢的是TCLAP——纯粹的模板化,因此没有库或链接,自动生成“——帮助”和其他好东西。请参阅文档中最简单的示例。
一个简单的解决方案是将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: }
这种方法肯定有很大的局限性,但我发现它很好地平衡了简单性和实用性。
Following from my comment and from rbaleksandar's answer, the arguments passed to any program in C are string values. You are provided the argument count (argc) which gives you the argument indexes zero-based beginning with the name of the program currently being run (which is always argv[0]). That leaves all arguments between 1 - argc as the user supplied arguments for your program. Each will be a string that is contained in the argument vector (which is a pointer to an array of strings you will seen written as char *argv[], or equivalently as a function parameter char **argv) Each of the strings argv[1] to argv[argc-1] are available to you, you simply need to test which argument is which.
这将允许您分离,并使它们可用为命令(cmd),选项(opt)和最后的参数(arg)到您的cmd。
Now it is worth noting, that the rules of your shell (bash, etc..) apply to the arguments passed to your program, word-splitting, pathname and variable expansion apply before your code gets the arguments. So you must consider whether single or more commongly double-quoting will be required around any of your arguments to prevent the normal shell splitting that would otherwise apply (e.g. ls -al my file.txt would results in 4 user-supplied arguments to your code, while ls -al "my file.txt" or ls -al my\ file.txt which would result in the 3 your were expecting.
把所有这些放在一起,您的简短解析可以像下面这样完成。(你也可以自由地做你喜欢的,使用一个开关而不是嵌套的if等…)
#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
(需要注意的是,如果你的任务是构建一个命令字符串,你需要复制多个值来表示opt或arg,那么你不能再简单地使用指针,而需要创建存储,要么通过简单的数组声明而不是指针开始,或者你可以根据需要动态分配存储,例如malloc, calloc和/或realloc。然后,您将有可用的存储空间来复制和连接其中的值。)
如果这是你的挑战,那么在所有这些答案之间,你应该知道如何处理你的问题。如果你必须更进一步,实际上让你的程序执行带有opt和arg的cmd,那么你会想要查看fork来生成一个半独立的进程,在其中运行将执行你的cmd opt和arg,使用类似于execv或execvp的东西。祝你好运,如果你有进一步的问题,请发表评论。