• 文章
  • 如何解析命令行参数。
发布
2009年8月7日(最后更新:2013年7月5日)

如何解析命令行参数。

评分:3.9/5 (1231票)
*****

简介

命令行参数是在程序运行时由操作系统传递给程序的,当程序被另一个程序请求时,例如像这样的命令行解释器(“shell”)cmd.exe在Windows上,或者bash在Linux和OS X上。用户键入一个命令,shell调用操作系统来运行程序。这具体是如何实现的超出了本文的范围(在Windows上,请查阅CreateProcess;在UNIX和类UNIX系统上,请查阅fork(3)exec(3)手册)。

命令行参数的用途多种多样,但最主要的两个是
  1. 修改程序行为 - 命令行参数可用于告诉程序您期望它如何工作;例如,某些程序有一个-q(静默)选项,用于告诉它们不要输出太多文本。
  2. 让程序在没有用户交互的情况下运行 - 这对于从脚本或其他程序调用的程序特别有用。

命令行

向程序添加解析命令行参数的功能非常简单。每个C和C++程序都有一个main函数。在不具备解析命令行能力的程序中,main通常定义如下
 
int main()

要查看命令行,我们必须为main函数添加两个参数,按照惯例,它们分别命名为argcargument count,参数数量)和argvargument vector,参数向量【此处,vector指数组,而非C++或欧几里得向量】)。argc的类型是intargv通常的类型是char**char* [](见下文)不同。main现在看起来像这样
 
int main(int argc, char* argv[]) // or char** argv 


argc告诉您有多少个命令行参数。它至少是1,因为第一个字符串argv (argv[0]是调用程序的命令。argv包含实际的命令行参数作为一个字符串数组,其中第一个(正如我们已经发现的)是程序的名称。尝试这个例子
1
2
3
4
5
6
7
#include <iostream>

int main(int argc, char* argv[])
{
    std::cout << argv[0] << std::endl;
    return 0;
}

此程序将打印您用于运行它的命令的名称:如果您调用可执行文件“a.exe”(Windows)或“a.out”(UNIX),它可能会分别打印“a.exe”或“./a.out”(如果您从shell运行它)。

前面提到argc包含传递给程序的参数数量。这很有用,因为它可以告诉我们在用户未传递正确数量的参数时,然后我们可以告知用户如何运行我们的程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>

int main(int argc, char* argv[])
{
    // Check the number of parameters
    if (argc < 2) {
        // Tell the user how to run the program
        std::cerr << "Usage: " << argv[0] << " NAME" << std::endl;
        /* "Usage messages" are a conventional way of telling the user
         * how to run a program if they enter the command incorrectly.
         */
        return 1;
    }
    // Print the user's name:
    std::cout << argv[0] << "says hello, " << argv[1] << "!" << std::endl;
    return 0;
}

示例输出(未传递参数)
用法:a.exe <姓名>
示例输出(传递了一个参数)
a.exe 向 Chris 问好!

参数和选项

参数(Arguments)和选项(Parameters)是传递给程序的字符串,用于向程序提供信息。例如,一个用于移动文件的程序可以带两个参数——源文件和目标文件move /path/to/source /path/to/destination(注意:在Windows上,这些路径将使用反斜杠【并且可能有一个驱动器前缀,如C】,然而,由于Windows支持路径中的反斜杠和正斜杠,而UNIX系统仅支持正斜杠,因此本文将始终使用正斜杠)。

在此示例中,程序将如下所示
1
2
3
4
5
6
7
8
9
10
11
#include <iostream>

int main(int argc, char* argv[])
{
    if (argc < 3) { // We expect 3 arguments: the program name, the source path and the destination path
        std::cerr << "Usage: " << argv[0] << "SOURCE DESTINATION" << std::endl;
        return 1;
    }
    return move(argv[1], argv[2]);  // Implementation of the move function is platform dependent
                    // and beyond the scope of this article, so it is left out.
}


如果我们想允许使用多个源路径,我们可以使用循环和std::vector
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <string>
#include <vector>

int main(int argc, char* argv[])
{
    if (argc < 3) { // We expect 3 arguments: the program name, the source path and the destination path
        std::cerr << "Usage: " << argv[0] << "SOURCE DESTINATION" << std::endl;
        return 1;
    }
    std::vector <std::string> sources;
    std::string destination;
    for (int i = 1; i < argc; ++i) { // Remember argv[0] is the path to the program, we want from argv[1] onwards
        if (i + 1 < argc)
            sources.push_back(argv[i]); // Add all but the last argument to the vector.
        else
            destination = argv[i];
    }
    return move(sources, destination);


参数可以作为选项的值传递。选项通常在UNIX上以单个连字符(-)表示“短选项”,或以双连字符(--)表示“长选项”,或在Windows上以正斜杠表示。本文将使用连字符(单双连字符)。继续以move程序为例,该程序可以使用-d/--destination选项来告诉它哪个路径是源文件,哪个是目标文件,如move -d /path/to/destination /path/to/sourcemove --destination /path/to/destination /path/to/source。选项总是右结合的,这意味着选项的参数始终是紧跟其右边的文本。

让我们扩展之前的示例以使用目标选项。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <string>
#include <vector>

int main(int argc, char* argv[])
{
    if (argc < 3) {
        std::cerr << "Usage: " << argv[0] << "--destination DESTINATION SOURCE" << std::endl;
        return 1;
    }
    std::vector <std::string> sources;
    std::string destination;
    for (int i = 1; i < argc; ++i) {
        if (std::string(argv[i]) == "--destination") {
            if (i + 1 < argc) { // Make sure we aren't at the end of argv!
                destination = argv[i++]; // Increment 'i' so we don't get the argument as the next argv[i].
            } else { // Uh-oh, there was no argument to the destination option.
                  std::cerr << "--destination option requires one argument." << std::endl;
                return 1;
            }  
        } else {
            sources.push_back(argv[i]);
        }
    }
    return move(sources, destination);
}

现在参数可以按任意顺序排列,只要目标路径紧跟在“--destination”的右侧。

更多关于用法消息

我们的用法消息很有帮助,但如果我们必须从多个地方打印它,我们就必须复制代码。显然,解决这个问题的方法是使用函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <string>
#include <vector>

static void show_usage(std::string name)
{
    std::cerr << "Usage: " << argv[0] << " <option(s)> SOURCES"
              << "Options:\n"
              << "\t-h,--help\t\tShow this help message\n"
              << "\t-d,--destination DESTINATION\tSpecify the destination path"
              << std::endl;
}

int main(int argc, char* argv[])
{
    if (argc < 3) {
        show_usage(argv[0]);
        return 1;
    }
    std::vector <std::string> sources;
    std::string destination;
    for (int i = 1; i < argc; ++i) {
        std::string arg = argv[i];
        if ((arg == "-h") || (arg == "--help")) {
            show_usage(argv[0]);
            return 0;
        } else if ((arg == "-d") || (arg == "--destination")) {
            if (i + 1 < argc) { // Make sure we aren't at the end of argv!
                destination = argv[i++]; // Increment 'i' so we don't get the argument as the next argv[i].
            } else { // Uh-oh, there was no argument to the destination option.
                  std::cerr << "--destination option requires one argument." << std::endl;
                return 1;
            }  
        } else {
            sources.push_back(argv[i]);
        }
    }
    return move(sources, destination);
}

现在,用户无需猜测,就可以使用-h--help选项来调用我们的程序,以了解如何运行该命令。


Getopt

这些查找命令行参数的方法简单且不太健壮。查找选项的最佳方法是使用 **getopt** 系列函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <unistd.h>

int getopt(int argc, char * const argv[],
       const char *optstring);

extern char *optarg;
extern int optind, opterr, optopt;

#include <getopt.h>

int getopt_long(int argc, char * const argv[],
                const char *optstring,
                const struct option *longopts, int *longindex);

int getopt_long_only(int argc, char * const argv[],
                     const char *optstring,
                     const struct option *longopts, int *longindex);

(来自手册页

手册页中有如何使用它们的示例。