【问题标题】:Parsing Command Line Arguments in C++? [closed]在 C++ 中解析命令行参数? [关闭]
【发布时间】:2010-10-26 07:48:35
【问题描述】:

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

prog [-abc] [input [output]]

标准库中是否有一些方法可以做到这一点,还是我需要编写自己的代码?


相关:

【问题讨论】:

  • 我认为 Nunit 源代码 (C#) 有一个很好的命令行处理类示例......
  • 最简单的方法是使用参数解析库之一:getoptargparse
  • 如果你不能使用库(例如 boost),至少使用std::vector<std::string> args(argv, argv+argc); 这样你就可以解析字符串向量而不是字符数组数组。
  • 对于已经在他们的程序中使用 OpenCV 的人,cv::CommandLineParser 也可能是一个不错的选择。 [我的意思是,以防万一您已经将它用于其他目的,我的意思并不是要将 OpenCV 包含在命令行解析器中。]
  • 最近为现代 c++ 写了这个:github.com/juzzlin/Argengine

标签: c++ command-line-arguments


【解决方案1】:

我在一些项目中使用了 GetPot: http://getpot.sourceforge.net/

主要特点:一切都在一个头文件中,没有构建麻烦。只需将其保存在您机器上的某个位置,然后将其“#include”到您的文件中,其中包含main()

最近没有更新,但文档很好,运行良好。

【讨论】:

  • 它从函数返回false,应该返回char*,这怎么可能? (函数是内联的 const char* GetPot::__match_starting_string(const char* StartString))
  • @Asalle 不知道,但这似乎是一个“内部”函数,而不是 API 的一部分。如有疑问,最好询问作者。
【解决方案2】:

一个简单的解决方案是将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: }

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

【讨论】:

    【解决方案3】:

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

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

    在您的main() 顶部。这会将所有命令行参数复制到std::strings 的向量中。然后您可以使用== 轻松比较字符串,而不是无休止的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 - 已修复。]

    【讨论】:

    • 添加了一个简单的例子。实际上,vector 给你带来的只是与 == 的简单比较和方便的按值复制。
    • 这无助于完成 argc/argv 无法完成的任何事情。
    • @brofield:当然,它不会改变世界。但我发现 == 和 value 语义足够简单,我一直在使用它。
    • @j_random_hacker - 如果我想记住参数的顺序怎么办?例如,如果用户键入mycommand.exe -h file.csv,我想告诉他们他们没有正确使用该实用程序以及原因(如果他们只是使用该版本,则不应提供文件名)。这个例子相当简单,但我可以想到更复杂的标志。最终结果将是:有时顺序确实很重要,有时则不重要。那么......我应该如何进行呢?如果您对我的问题有任何疑问,请告诉我。
    • @Hamish:我有点困惑——将字符串加载到向量中不会“丢失”它们的顺序。您仍然可以使用args[i] 访问第 i 个参数(实际上我自己经常这样做,正如我的代码 sn-p 中的注释所说)。如果您一次只需要处理一个,迭代器样式会更方便一些。这能回答你的问题吗?
    【解决方案4】:

    我认为 GNU GetOpt 不太好用。

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

    所以我自己实现了一个解析器,它产生一个 std::map<: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;
    }
    

    【讨论】:

    • 你说的是什么意思"...不太直接用"?你能详细说明一下吗?
    【解决方案5】:

    在堆栈溢出答案中包含它有点太大了,但我创建了一个用于以声明方式定义命令行的库。它利用 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 指针的需要,但我还没有找到不引入宏的方法。这些指针允许每个成员向驱动解析的基类中的表注册自己。

    一旦你有了一个实例,就会有几个方法重载来解析来自字符串或main-style 参数向量的输入。解析器处理 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 行(不包括测试)。实例有点臃肿,因为解析表是实例的一部分(而不是类)。但是通常每个程序只需要一个实例,这种纯声明式方法的便利性非常强大,只需解析新输入即可重置实例。

    【讨论】:

      【解决方案6】:

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

      【讨论】:

      • 这是开箱即用的。但是请注意,选项参数是const std::string&amp;。重要的是 std::find 的 value 参数是 std::string 以便使用 std::string::operator==() 而不是 char * operator==()(因为后者只会比较指针值而不是字符串内容)。
      • 这不能按预期工作,例如,像 tar 应用程序:tar -xf file,对吧?每个选项都必须分开。 grep -ln pattern file 不会被理解,但必须是 grep -l -n pattern file
      • 绝对如果你想要posix风格的命令行选项,那么你应该使用其他答案中提到的命令行处理库之一。正如这个答案所说,这是用于简单的命令行选项。
      • 这很好,但需要进行两个小改进:首先,构造函数参数应该是 const 限定的,其次,getCmdOption 的返回值应该是一个值,而不是一个引用,否则你会遇到@987654321 @。除此之外,这是一个不错且简单的解决方案,我会使用它,谢谢。
      • 1. @iain 那段代码的许可证是什么? 2. @TomášDvořák 关于返回对临时的引用是正确的,当getCmdOption 返回空字符串时。 InputParser 应该有一个 std::string empty_string 作为成员,并在找不到选项时返回其引用。
      【解决方案7】:

      我在 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;
      ...
      

      【讨论】:

        【解决方案8】:

        命令基本上是一个字符串。一般来说,它可以分为两部分——命令的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,我们会得到相同的输出),使用短名称(通常是单个字母,但有时更多;缩写)或长名称(在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&lt;std::string&gt; args(argv, argv+argc); 部分只是处理字符串数组的一种更简单的 C++ 方式,因为 char * 是 C 风格的字符串(char *argv[] 是此类字符串的数组),可以轻松转换为一个 C++ 字符串,它是 std::string。然后我们可以通过给出argv的起始地址,然后也指向它的最后一个地址,即argv + argc,将所有转换后的字符串添加到一个向量中(我们将argc的字符串编号添加到argv的基地址中,即基本上指向我们数组的最后一个地址)。

        在上面的for 循环中,您可以看到我检查(使用简单的if-else)某个参数是否可用,如果是,则相应地处理它。 请注意:通过使用这样的循环,参数的顺序无关紧要。正如我在开头提到的,一些命令实际上对它们的部分或全部参数有严格的顺序。您可以通过手动调用每个 args 的内容(或 argv,如果您使用初始 char* 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 的值)等。根据您手头的任务可能会发疯并为您的命令行参数增加疯狂的复杂性。

        希望这会有所帮助。如果有不清楚的地方或者您需要更多示例,请告诉我。

        【讨论】:

          【解决方案9】:

          如果可以的话,我也建议看看我写的一个选项解析库:dropt

          • 它是一个 C 库(如果需要,可以使用 C++ 包装器)。
          • 它很轻。
          • 它是可扩展的(可以轻松添加自定义参数类型,并且与内置参数类型具有同等地位)。
          • 它应该是非常可移植的(它是用标准 C 编写的),没有依赖关系(C 标准库除外)。
          • 它有一个非常不受限制的许可证 (zlib/libpng)。

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

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

          如果你想使用bar,但禁用--flag1,它允许你这样做:

          bar --flag1=0
          

          【讨论】:

          • 这看起来很整洁。很高兴我向下滚动;纯 C 没有什么好用的,保存这个!
          • 听起来不错,但似乎有点太大了。另外,我只是在寻找一种从参数中解析出 scanf 格式说明符的方法,我已经编写了自己的(尽管更基本的)解析器。
          • @MarcusJ 你说它太大(它比大多数其他命令行选项解析器小得多)但你希望它解析 printf/scanf 格式说明符(它不是命令行选项解析器通常会做的事情)...
          • 是的,我知道我对此有一些具体要求,我将继续重写我的选项解析器,但我确实查看了您的代码和传入结构的想法包含各种选项真的很有趣(到目前为止我的只是硬编码)。它本身并没有太大,只是我有一个 .c/.h 项目,而你的代码会使我已经拥有的代码量翻倍,所以对于我的具体项目来说太大了。跨度>
          【解决方案10】:

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

          这将允许您将它们分开并作为 command (cmd)、options (opt) 和最后的 参数 (arg) 到你的cmd

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

          将所有这些放在一起,您的简短解析可以完成如下所示。 (您也可以随意使用switch 而不是嵌套的ifs 等...)

          #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 以生成一个半独立的进程,在该进程中运行将执行您的cmd optargexecvexecvp 类似。祝您好运,如果您有其他问题,请发表评论。

          【讨论】:

            【解决方案11】:

            你的 C/C++ 程序总是有一个 main 函数。看起来是这样的:

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

            这里的 argc 是一些命令行参数,它们已经传递给你的程序,而 argv 是一个带有这些参数的字符串数组。所以命令行被调用者进程分隔成参数(这不是一行,就像在 Windows 中一样)。

            现在你需要整理它们:

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

            【讨论】:

              【解决方案12】:

              还有一个Google library 可用。

              真的,命令行解析已经“解决”了。随便挑一个吧。

              【讨论】:

              • (感谢@QPaysTaxes 注意到链接已损坏;我不知道您的编辑为什么被拒绝,但您绝对是正确的)。
              • 我想不出对问题的帮助不大的回答。 '解决了。选一个。'对不起,但是“呃”。用我大约 15 分钟的时间思考这个问题,我想出了大约 30 种不同的场景来解决这个问题。我怀疑“正确”的响应更类似于解释一组特定的关注点如何导致一组特定的代码实现。但是,嘿,谢谢你的来电。
              【解决方案13】:

              我可以建议Templatized C++ Command Line Parser Library(一些forks on GitHub 可用),API 非常简单并且(引自网站):

              该库完全在头文件中实现,因此很容易 与其他软件一起使用和分发。它在麻省理工学院获得许可 无忧分发许可证。

              这是手册中的一个示例,为简单起见,在此处着色:

              #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; }
              }
              

              【讨论】:

              • 这个选项对我来说是最简单的,尽管它确实为我的程序添加了一个包含许多头文件的子目录。需要相应地编辑包含路径。
              • 这些年来我使用过各种解决方案,包括我自己自制的解决方案。我和其他人一起赞美 TCLAP 的美德。它很容易集成并满足我的需求。
              • 看来项目已移至sourceforge
              • @JohnShedletsky 你确定吗?我不再使用该库,但在 manual 中显示了长格式和短格式参数。
              • 没有投反对票,但我个人不喜欢这种依赖异常的小东西。
              【解决方案14】:

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

              如果您有接受参数的选项,您也可以使用optarg

              【讨论】:

              • 呃。我了解这个库在 C 代码中的使用,但是 IMO,这太低级了,在我编写的任何 C++ 应用程序中都无法接受。如果您不需要纯 C,请寻找更好的库。
              • 还有一个 getopt 的 GNU 示例,其值为例如myexe -c myvalue 可以尝试搜索“Example of Parsing Arguments with getopt”
              • 这是一个由 GNU 自己提供的example 示例^^
              【解决方案15】:

              您可以使用 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;
              }
              

              【讨论】:

              • 仅供参考,GNU getopt 是 GPL,而 getoptpp 也是 GPL,因此 boost 变体可能更适合非开源软件。
              • @SorinSbarnea, TINLA,但我相信许可证是 actually LGPLv2
              • 对不起,但项目页面上的 Google 代码许可清楚地说明了 GPL。
              • @SorinSbarnea,你看我的链接了吗?我应该更清楚一点,但我指的是 getopt 和 getopt-gnu,而不是 getoptpp。
              • Getopt 对于 C++ 程序来说是愚蠢的低级。我强烈建议不要将它用于 C++ 程序。
              【解决方案16】:

              你可以试试我的 options 小标题(166 loc 很容易破解)options.hpp。它是一个单头实现,应该按照您的要求进行。它还会自动打印帮助页面。

              【讨论】:

                【解决方案17】:

                Qt 5.2 带有command line parser 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 用法:./qtopt [选项] infile 做 X 的工具。 选项: -h, --help 显示此帮助。 -v, --version 显示版本信息。 -+ 冗长 -o, --output 输出文件。 论据: infile 输入文件。

                自动生成的版本输出:

                $ ./qtopt -v 工具X 1.2

                一些真实的电话:

                $ ./qtopt b1 -+ -o tmp blah.foo 输入文件: ("b1", "blah.foo") ,详细:true ,输出:"tmp" $ ./qtop 输入文件: () ,详细: false ,输出:“out”

                解析错误:

                $ ./qtopt --hlp 未知选项“hlp”。 $回声$? 1

                结论

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

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

                【讨论】:

                  【解决方案18】:

                  Boost.Program_options 应该可以解决问题

                  【讨论】:

                  • 不错的选择。或者,如果您由于某种原因不能使用 boost,那么基于标准 c 的“getopt”函数也可以完成工作。
                  • boost::program_options 的文档可能会更完整。很难找到如何使用文件来保留选项,这是一个关键功能。
                  • 只为了解析命令行选项而在代码库中引入增强功能有点“敲竹杠”。如果 boost 已经存在,请使用它。否则看看像 gopt 这样的东西。总的来说,没有什么不反对 boost,但它有点重量级,我发现这些版本与 g++ 版本紧密相关。
                  • 此外,boost::program_options 不是一个只有头文件的库。你必须建立提升。这很麻烦。
                  • 对于这项任务来说,提升似乎完全是矫枉过正
                  【解决方案19】:

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

                  【讨论】:

                  • 我用过 getopt、google 的 gflags、Boost 的 program_options 和 tclap 是 fantastic。关于 tclap,我不能说太多好话,尤其是考虑到可用的替代方案。我的抱怨程度在于它的帮助格式与我的眼睛习惯的“不同”。
                  【解决方案20】:
                  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);
                      }
                  
                  }
                  

                  【讨论】:

                  • -1:这会在没有绑定检查的情况下获取数组元素
                  • -1:因为没有边界检查
                  • @RobertMunafo:对argv[i+1] 的引用很容易超出argv 数组的范围。考虑使用 "-i" 作为 last 参数运行程序。
                  • -1 是一个“自己动手”的答案,而该问题专门要求一个“STL 中的库”来完成该任务。此外,正如 Keith Thompson 和 mmutz 所指出的,边界检查存在一些错误。
                  • 我发现 cmets 真的很苛刻。我认为还可以展示一个示例,说明如何在不使用库的情况下完成此操作。这个答案是补充 +1,对不起。
                  【解决方案21】:

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

                  http://optionparser.sourceforge.net

                  它是一个只有头文件的库(实际上只是一个头文件),与所有其他建议不同,它是 也是独立的,即它没有任何依赖关系。特别是不依赖于 STL。它甚至不使用异常或任何其他需要库支持的东西。这意味着它可以与纯 C 或其他语言链接,而无需引入“外来”库。

                  与 boost::program_options 一样,它的 API 提供了对选项的便捷直接访问, 即您可以编写这样的代码

                  if (options[HELP]) ... ;

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

                  与 boost::program_options 不同,但这只是使用一个以(用户提供的)枚举为索引的数组。这提供了没有重量的关联容器的便利性。

                  它有很好的文档记录,并拥有适合公司使用的许可证 (MIT)。

                  TLMC++OP 包含一个很好的使用消息格式化程序,可以做 换行和列对齐在本地化程序时很有用,因为它确保即使在消息较长的语言中输出看起来也不错。它还为您节省了手动格式化 80 列的使用格式的麻烦。

                  【讨论】:

                  • 链接以供将来参考:sourceforge.net/projects/optionparser
                  • 在我看来,非常容易出错,例如。 G。我在使用它时在段错误之后(在 lib 代码中)收到了段错误
                  【解决方案22】:

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

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

                  【讨论】:

                  • 类似于“The Lean Mean C++ Option Parser”,但有更好的 API。强烈推荐。
                  • 它不支持Windows以外的任何其他操作系统
                  • @Asalle 是什么让你说它只是 Windows?
                  • 它使用 strcpy_s。通过编写我自己的调用 strcpy 的又快又脏的 strcpy_s,我很幸运地让它在 macos 上工作。
                  【解决方案23】:

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

                  【讨论】:

                  • 从 0.1.3 版开始,许可证现在是 MIT。我正在一个新项目而不是 TCLAP 上尝试这个,到目前为止它看起来很有希望。文件配置选项非常好。
                  • 我刚刚试用了exOptionParser,但它有很多问题。首先,我收到 58 条关于 unsigned int 到 int 转换的警告。它还尝试增加列表迭代器(不能像那样使用)并崩溃。它的界面也很糟糕。它在所有地方都使用引用,而不仅仅是返回您想要的数据。尽管它是在 C++ STL 之上构建的,但它看起来像一个 C 库。
                  • 注意;检测未知参数不起作用。此外,如果没有放在其他标头之前,标头会产生编译错误。我会寻找另一个解析器..
                  【解决方案24】:

                  试试 CLPP 库。它是用于命令行参数解析的简单灵活的库。仅标题和跨平台。仅使用 ISO C++ 和 Boost C++ 库。恕我直言,它比 Boost.Program_options 更容易。

                  图书馆:http://sourceforge.net/projects/clp-parser

                  2010 年 10 月 26 日 - 新版本 2.0rc。修复了许多错误,对源代码、文档、示例和 cmets 进行了全面重构。

                  【讨论】:

                    【解决方案25】:

                    试试 CLPP 库。它是用于命令行参数解析的简单灵活的库。仅标题和跨平台。仅使用 ISO C++ 和 Boost C++ 库。恕我直言,它比 Boost.Program_options 更容易。

                    图书馆:http://sourceforge.net/projects/clp-parser/

                    2010 年 10 月 26 日 - 新版本 2.0rc。修复了许多错误,对源代码、文档、示例和 cmets 进行了全面重构。

                    【讨论】:

                      【解决方案26】:

                      试试 CLPP 库。它是用于命令行参数解析的简单灵活的库。仅标题和跨平台。仅使用 ISO C++ 和 Boost C++ 库。恕我直言,它比 Boost.Program_options 更容易。

                      图书馆:http://sourceforge.net/projects/clp-parser

                      2010 年 10 月 26 日 - 新版本 2.0rc。修复了许多错误,对源代码、文档、示例和 cmets 进行了全面重构。

                      【讨论】:

                        【解决方案27】:

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

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

                        【讨论】:

                        • 问题不是关于 C++ 的,Getopt 只是普通的 C。曾经有它的 C++ 变体,但由于某种原因它被撤回了。
                        • 它在 c++ 中运行良好;它是我们在所有 c++ 代码中使用的。
                        • 嗯,是的,但你可以做得更好,例如TCLAP。我用新的选项定义添加或删除了一行,我不需要在其他地方编辑代码——>老派 getopt 不是这样。
                        【解决方案28】:

                        【讨论】:

                          【解决方案29】:

                          您可能希望为此使用外部库。有很多可供选择。

                          Boost 有一个功能非常丰富(和往常一样)的库Boost Program Options

                          过去几年我个人最喜欢的是TCLAP——纯模板化,因此没有库或链接、自动“--help”生成和其他好东西。请参阅文档中的 simplest example

                          【讨论】:

                          • +1,不知道 tclap,它设法轻量级,但感觉很完整,我肯定会更深入地研究。
                          【解决方案30】:

                          这是我最喜欢的命令行方式,尤其是在效率成为问题的情况下。这似乎有点矫枉过正,但我​​认为这种矫枉过正的缺点很少。

                          Use gperf for efficient C/C++ command line processing

                          缺点:

                          • 您必须先运行一个单独的工具才能在 C/C++ 中生成哈希表的代码
                          • 不支持特定的命令行界面。例如,用一个破折号声明多个选项的 posix 速记系统“-xyz”很难实现。

                          优点:

                          • 您的命令行选项与您的 C++ 代码分开存储(在单独的配置文件中,不需要在运行时读取,只需在编译时读取)。
                          • 您的代码中只有一个开关(打开枚举值)来确定您有哪个选项
                          • 效率为 O(n),其中 n 是命令行上的选项数,可能的选项数无关紧要。最慢的部分可能是 switch 的实现(有时编译器倾向于像 else 块一样实现它们,这会降低它们的效率,尽管如果您选择连续值则不太可能,请参阅:this article on switch efficiency
                          • 分配用于存储关键字的内存恰好足以容纳关键字集,并且不会更大。
                          • 也适用于 C

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

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

                          因此,您的软件中的代码极其简洁明了,而且您无需手动更改一个自动生成的哈希表文件。我怀疑 boost::program_options 是否真的会击败它,即使没有效率作为优先事项。

                          【讨论】:

                            最近更新 更多