• 文章
  • 如何解析命令列引數。
釋出
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);

(來自手冊頁

手冊頁中有如何使用它們的示例。