【C 高階】解析命令參數方法

C 高階系列導航


1. C 程序的參數

在命令行中執行應用軟件時,常使用參數來完成對應用的配置或設置。因此,程序中對命令參數進行解析是非常常見且重要的功能。

在 C 語言中,當程序需要使用命令參數時,mian 函數必須爲以下形式:

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

參數對應着 main 函數的形參 argcargvargc 爲命令的參數列表的個數,需注意命令本身就是參數之一,例如命令 ./app 的參數數爲 1。argv 爲參數列表,例如命令 ./app -m -p 的選項參數 -m 對應 argv[1]-p 對應 argv[2]

以下實驗將打印命令的參數列表:

#include <unistd.h>
int main(int argc, char* argv[])
{
    for (int i = 0; i < argc; i++)
    {
        printf("argv[%d] : %s\n", i, argv[i]);
    }
}

編譯後執行命令:output/Kapp -s -p 12345 -m,打印結果如下:

argv[0] : output/Kapp
argv[1] : -s
argv[2] : -p
argv[3] : 12345
argv[4] : -m

可以看到,逐個解析命令參數並不困難。但當參數爲選項類型如以上的 -p 時,此時往往需連續解析後一參數 12345 並把解析結果拼接處理,這樣程序設計的複雜度將大大增加且擴展性很差。

有沒有方法可以方便管理選項類型參數呢?答案是有的。C 庫提供了 getopt 函數與 getopt_long 函數來管理命令的選項類型參數。

爲了簡化命名,以下將把選項類型參數簡稱爲選項,其餘皆稱爲參數,本質上兩者都是命令的參數。


2. getopt 函數

getopt 函數所屬頭文件 <unistd.h>,函數原型爲:

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

形參 argcargv 對應的就是 mian 函數的形參 argcargv,可以直接使用 main 函數的形參作爲傳參。

形參 optstring 用於註冊選項規則,表示以指定規則去解析 argv 中的選項,不同的規則可處理不同的選項情景。

optstring 規則如下:

格式 含義 示例
單字符 該選項無參數 "m" 表示選項 -m 無後續參數
單字符後接一個冒號 該選項必要有參數 "p:" 表示選項 -p 的形式必須爲 -p(parameter)-p (parameter)
單字符後接兩個冒號 該選項的參數爲可選的 "n::" 表示選項 -n 的形式可以爲 -n-n(parameter),但不能爲 -n (parameter)(選項與參數間也有空格)

需要注意的是,選項的格式必須爲 '-' + 字符,getopt 函數纔會認爲該參數爲選項類型,否則會直接忽略。而命令選項是支持連續在一起的,即 -a -b -c 可合併爲 -abc。不過最好只把相同規則的選項合併使用,不同規則的選項會導致解析結果混淆進而出錯。

getopt 函數的返回值存在以下規定:

  • 當以規則 optstring 解析選項成功時,將返回選擇字符對應的 ASCII 碼值。
  • 當以規則 optstring 解析選項失敗時,將返回 '?' 對應的 ASCII 碼值。
  • 當全部參數解析完成時,將返回 -1。

想必不少人到這裏都會有些懵圈:使用 getopt 函數解析選項成功後,解析結果哪去了呢?

其實在 <unistd.h> 包含的頭文件 <getopt.h> 中有以下兩個全局變量聲明:

extern char* optarg;
extern int optind;

getopt 函數一次只解析一個選項。optarg 指向的是解析後 argv 中選項對應的參數結果,例如以規則 "p:" 去解析 "-p 12345" 後,optarg 指向的是 "12345"。當解析後無參數時,optarg 將指向 NULL。

optind 保存下一個解析參數的下標,該下標從 0 起算。

以下實驗將使用 getopt 來打印各命令選項及其參數:

#include <unistd.h>
int main(int argc, char* argv[])
{
    int ret = 0;
    while ((ret = getopt(argc, argv, "ab:c::")) != -1)
    {  
        printf("ret = %c\t\t", ret);
        printf("optarg = %s\t\t", optarg);
        printf("optind = %d\t\t", optind);
        printf("argv[optind] = %s\n", argv[optind]);
    }  
}

編譯後,執行命令:output/Kapp -a -a1,打印結果如下:

ret = a		optarg = (null)		optind = 2		argv[optind] = -a1
ret = a		optarg = (null)		optind = 2		argv[optind] = -a1
output/Kapp: invalid option -- '1'
ret = ?		optarg = (null)		optind = 3		argv[optind] = (null)

命令中“-a”對應的規則爲 "a",即單字符。所以程序解析命令選項實際先解析了選項 -a,再解析選項 -a,最後解析選項 -1 失敗(-a1 等價於 -a -1)。因此,getopt 函數解析了 3 次命令參數。

執行命令:output/Kapp -b0 -b -b123 -b 465,打印結果如下:

ret = b		optarg = 0		    optind = 2		argv[optind] = -b
ret = b		optarg = -b123		optind = 4		argv[optind] = -b
ret = b		optarg = 465		optind = 6		argv[optind] = (null)

命令中“-b”對應的規則爲 "b:",即單字符接一個冒號。所以程序解析命令選項實際先解析了選項 -b0 得到參數 "0",再解析選項 -b 得到參數 "-b123",最後解析選項 -b 得到參數 "456"。因此,使用規則單字符接一個冒號時對應的命令選項必須有參數,否則會把下一個選項誤認爲該規則對應的參數。

執行命令:output/Kapp -c -c100 -c 200,打印結果如下:

ret = c		optarg = (null)		optind = 2		argv[optind] = -c100
ret = c		optarg = 100		optind = 3		argv[optind] = -c
ret = c		optarg = (null)		optind = 4		argv[optind] = 200

命令中“-c”對應的規則爲 "c::",即單字符接兩個冒號。所以程序解析命令選項實際先解析了選項 -c ,再解析選項 -c100 得到參數 "100",最後解析選項 -c,參數 200 將直接被忽略。

getopt 函數看起來已經能滿足基本的選項解析需求了,但熟悉 Linux 命令的開發者都知道命令選項除了有單破折號“-”的短選項形式外,還有雙破折號“–”的長選項形式,例如“–help”、“–prefix”等。顯然,getopt 函數無法滿足需求,此時可使用 getopt_long 函數。


3. getopt_long 函數

getopt_long 爲 GNU 在 getopt 函數基礎上所擴展的函數,所屬頭文件 <getopt.h>

函數原型如下:

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

其中,前 3 個參數與 getopt 函數是一致的。getopt_long 函數包含了 getopt 函數的功能,並由後兩個參數完成解析長選項的功能拓展。

形參 longopts 用於註冊長選項的規則。其所屬結構體類型 struct option<getopt.h> 中聲明如下:

struct option {
    const char* name;   // 選項名稱
    int has_arg;        // 指明參數類型
    int* flag;          // 當 flag 爲 NULL 時,返回 val;不爲空時而 *flag 爲 val,返回 0 
    int val;            // 用於指定函數找到選項時的返回值或 flag 非空時指定 *flag 的值
}; 

name 爲長選項的名稱,has_arg 爲選擇 name 對應的規則,其可選以下選項:

選項 含義 示例
no_argument 等同於短選項的單字符規則,表明長選項不帶參數 name"help" 表示選項 --help 無參數
required_argument 等同於短選項的單字符接一個冒號規則,表明長選項必須帶參數 name"prefix" 表示選項形式必須爲 --prefix (parameter)--prefix=(parameter)
optional_argument 等同於短選項的單字符接兩個冒號規則,表明長選項的參數是可選的 name"prefix" 表示選項形式必須爲 --prefix--prefix=(parameter)

longindex 用於記錄當前找到符合 longopts 的選項是第幾個元素的描述,即 longopts 的下標值。

getopt_long 函數的記錄解析結果與 getopt 函數一致,也是使用 optarg 來進行記錄。

以下實驗將使用 getopt_long 來打印各命令長選項:

#include <getopt.h>
int main(int argc, char* argv[])
{
    int ret = -1;
    int option_index = 0;
    struct option long_options[] =
    {  
        {"noarg",  no_argument,       NULL, 'n'},
        {"reqarg", required_argument, NULL, 'r'},
        {"optarg", optional_argument, NULL, 'o'},
        {NULL,     0,                 NULL,  0}
    }; 
    while ((ret = getopt_long(argc, argv, "", long_options, &option_index)) != -1)
    {  
        printf("ret = %c\t", ret);
        printf("optarg = %s\t", optarg);
        printf("optind = %d\t", optind);
        printf("argv[optind] = %s\t", argv[optind]);
        printf("option_index = %d\n", option_index);
    }  
}

編譯後,執行命令:output/Kapp --noarg --noarg=1,打印結果如下:

ret = n		optarg = (null)		optind = 2		argv[optind] = --noarg=1	option_index = 2
output/Kapp: option '--noarg' doesn't allow an argument
ret = ?		optarg = (null)		optind = 3		argv[optind] = (null)		option_index = 2

命令中的“–noarg”對應規則中的 "noarg"。所以程序解析命令選項實際先解析了選項 --noarg ,再解析選項 --noarg=1 解析失敗。

執行命令:output/Kapp --reqarg=100 --reqarg --reqarg=200 --reqarg 300,打印結果如下:

ret = r		optarg = 100		    optind = 2		argv[optind] = --reqarg		option_index = 0
ret = r		optarg = --reqarg=200	optind = 4	    argv[optind] = --reqarg		option_index = 0
ret = r		optarg = 300		    optind = 6		argv[optind] = (null)		option_index = 0

命令中的“–reqarg”對應規則中的 "reqarg"。所以程序解析命令選項實際先解析了選項 --reqarg=100 得到參數 "100" ,再解析選項 --reqarg 得到參數 "--reqarg=200",最後解析選項 --reqarg 得到參數 300。可以看到,如果命令中“–reqarg”後不接參數時,函數會把下一選項當作選項對應的參數。

執行命令:output/Kapp --optarg --optarg=100 --optarg 200,打印結果如下:

ret = o	    optarg = (null)		optind = 2		argv[optind] = --optarg=1		option_index = 1
ret = o		optarg = 100		optind = 3		argv[optind] = (null)			option_index = 1

命令中的“–optarg”對應規則中的 "optarg"。所以程序解析命令選項實際先解析了選項 --optarg ,再解析選項 --optarg=100 得到參數 "100",最後解析選項 --optarg,參數 200 將直接被忽略。


4. 參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章