天天看點

getopt處理指令行參數

Linux下很多程式甚至那些具有圖形使用者界面(graphical user interface,GUI)的程式,都能接受和處理指令行選項。對于某些程式,這是與使用者進行互動的主要手段。具有可靠的複雜指令行參數處理機制,會使得您的應用程式更好、更有用。getopt() 是一個專門設計來減輕指令行處理負擔的庫函數。

1、指令行參數

指令行程式設計的首要任務是解析指令行參數,GUI派的程式員很少關心這個。這裡,對參數采用了一種比較通俗的定義:指令行上除指令名之外的字元串。參數由多項構成,項與項之間用空白符彼此隔開。

參數進一步分為選項 和操作數 。選項用于修改程式的預設行為或為程式提供資訊,比較老的約定是以短劃線開頭。選項後可以跟随一些參數,稱為選項參數。剩下的就是操作數了。

2、POSIX約定

POSIX表示可移植作業系統接口: Portable Operating System Interface,電氣和電子工程師協會(Institute of Electrical and Electronics Engineers,IEEE)最初開發 POSIX 标準,是為了提高 UNIX 環境下應用程式的可移植性。然而,POSIX 并不局限于 UNIX。許多其它的作業系統,例如 DEC OpenVMS 和 Microsoft Windows NT,都支援 POSIX 标準。

下面是POSIX标準中關于程式名、參數的約定:

程式名不宜少于2個字元且不多于9個字元;

程式名應隻包含小寫字母和阿拉伯數字;

選項名應該是單字元活單數字,且以短橫‘-‘為前綴;

多個不需要選項參數的選項,可以合并。(譬如:foo -a -b -c ---->foo -abc)

選項與其參數之間用空白符隔開;

選項參數不可選。

若選項參數有多值,要将其并為一個字串傳進來。譬如:myprog -u "arnold,joe,jane"。這種情況下,需要自己解決這些參數的分離問題。

選項應該在操作數出現之前出現。

特殊參數‘--'指明所有參數都結束了,其後任何參數都認為是操作數。

選項如何排列沒有什麼關系,但對互相排斥的選項,如果一個選項的操作結果覆寫其他選項的操作結果時,最後一個選項起作用;如果選項重複,則順序處理。

允許操作數的順序影響程式行為,但需要作文檔說明。

讀寫指定檔案的程式應該将單個參數'-'作為有意義的标準輸入或輸出來對待。

3、GNU長選項

GNU鼓勵程式員使用--help、--verbose等形式的長選項。這些選項不僅不與POSIX約定沖突,而且容易記憶,另外也提供了在所有GNU工具之間保持一緻性的機會。GNU長選項有自己的約定:

對于已經遵循POSIX約定的GNU程式,每個短選項都有一個對應的長選項。

額外針對GNU的長選項不需要對應的短選項,僅僅推薦要有。

長選項可以縮寫成保持惟一性的最短的字串。

選項參數與長選項之間或通過空白字元活通過一個'='來分隔。

選項參數是可選的(隻對短選項有效)。

長選項允許以一個短橫線為字首。

4、基本的指令行處理技術

C程式通過argc和argv參數通路它的指令行參數。argc是整型數,表示參數的個數(包括指令名)。main()函數的定義方式有兩種,差別僅在于argv如何定義:

int main(int argc, char *argv[])

{

   ……

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

{

   ……

當 C 運作時庫的程式啟動代碼調用 main() 時,已經對指令行進行了處理。argc 參數包含參數的計數值,而 argv 包含指向這些參數的指針數組。argv[0]是程式名。

一個很簡單的指令行處理技術的例子是echo程式,它可以将參數輸出到标準裝置上,用空格符隔開,最後換行。若指令行第一個參數為-n,那麼就不會換行。

清單1:

#include <stdio.h>

int main(int argc, char **argv)

{

    int i, nflg;

    nflg = 0;

    if(argc > 1 && argv[1][0] == '-' && argv[1][1] == 'n'){

        nflg++;

        argc--;

        argv++;

    }

    for(i=1; i<argc; i++){

        fputs(argv[i], stdout);

        if(i < argc-1)

            putchar(' ');

    }

    if(nflg == 0)

        putchar('/n');

    return 0;

}

echo程式中,對于指令行參數的解析是手動實作的。很久以前,Unix支援小組為了簡化對于指令行參數的解析,開發了getopt()函數 ,同時提供了幾個外部變量,使得編寫遵守POSIX的代碼變得更加容易了。

5、指令行參數解析函數 —— getopt()

getopt()函數聲明如下:

#include <unistd.h>

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

extern char *optarg;

extern int optind, opterr, optopt;

該函數的argc和argv參數通常直接從main()的參數直接傳遞而來。optstring是選項字母組成的字串。如果該字串裡的任一字元後面有冒号,那麼這個選項就要求有選項參數。

當給定getopt()指令參數的數量 (argc )、指向這些參數的數組 (argv ) 和選項字串 (optstring ) 後,getopt() 将傳回第一個選項,并設定一些全局變量。使用相同的參數再次調用該函數時,它将傳回下一個選項,并設定相應的全局變量。如果不再有可識别的選項,将傳回 -1 ,此任務就完成了。

getopt() 所設定的全局變量包括:

char *optarg ——目前選項參數字串(如果有)。

int optind ——argv的目前索引值。當getopt()在while循環中使用時,循環結束後,剩下的字串視為操作數,在argv[optind]至argv[argc-1]中可以找到。

int opterr——這個變量非零時,getopt()函數為“無效選項”和“缺少參數選項,并輸出其錯誤資訊。

int optopt ——當發現無效選項字元之時,getopt()函數或傳回'?'字元,或傳回':'字元,并且optopt包含了所發現的無效選項字元。

以下面的程式為例:

選項:

-n —— 顯示“我的名字”。

-g —— 顯示“我女朋友的名字”。

-l —— 帶參數的選項.

清單2:

#include <stdio.h>

#include <unistd.h>

int main (int argc, char **argv)

{

    int oc;                    

    char *b_opt_arg;           

    while((oc = getopt(argc, argv, "ngl:")) != -1)

    {

        switch(oc)

        {

            case 'n':

                printf("My name is Lyong./n");

                break;

            case 'g':

                printf("Her name is Xxiong./n");

                break;

            case 'l':

                b_opt_arg = optarg;

                printf("Our love is %s/n", optarg);

                break;

        }

    }

   return 0;

}

運作結果:

$ ./opt_parse_demo -n

My name is Lyong.

$ ./opt_parse_demo -g

Her name is Xxiong.

$ ./opt_parse_demo -l forever

Our love is forever

$ ./opt_parse_demo -ngl forever

My name is Lyong.

Her name is Xxiong.

Our love is forever

6、改變getopt()對錯誤指令行參數資訊的輸出行為

不正确的調用程式在所難免,這種錯誤要麼是指令行選項無效,要麼是缺少選項參數。正常情況下,getopt()會為這兩種情況輸出自己的出錯資訊,并且傳回'?'。為了驗證此事,可以修改一下上面的清單2中的代碼。

清單3:

#include <stdio.h>

#include <unistd.h>

int main (int argc, char **argv)

{

    int oc;                    

    char *b_opt_arg;           

    while((oc = getopt(argc, argv, "ngl:")) != -1)

    {

        switch(oc)

        {

            case 'n':

                printf("My name is Lyong./n");

                break;

             case 'g':

                printf("Her name is Xxiong./n");

                break;

            case 'l':

                b_opt_arg = optarg;

                printf("Our love is %s/n", optarg);

                break;

            case '?':

                printf("arguments error!/n");

                break;

        }

    }

    return 0;

}

輸入一個錯誤的指令行,結果如下:

$ ./opt_parse_demo -l

./opt_parse_demo: option requires an argument -- l

arguments error!

如果不希望輸出任何錯誤資訊,或更希望輸出自定義的錯誤資訊。可以采用以下兩種方法來更改getopt()函數的出錯資訊輸出行為:

在調用getopt()之前,将opterr設定為0,這樣就可以在getopt()函數發現錯誤的時候強制它不輸出任何消息。

如果optstring參數的第一個字元是冒号,那麼getopt()函數就會保持沉默,并根據錯誤情況傳回不同字元,如下:

“無效選項” —— getopt()傳回'?',并且optopt包含了無效選項字元(這是正常的行為)。

“缺少選項參數” —— getopt()傳回':',如果optstring的第一個字元不是冒号,那麼getopt()傳回'?',這會使得這種情況不能與無效選項的情況區分開。

清單4:

#include <stdio.h>

#include <unistd.h>

int main (int argc, char **argv)

{

    int oc;                    

    char ec;                            

    char *b_opt_arg;           

    while((oc = getopt(argc, argv, ":ngl:")) != -1)

    {

        switch(oc)

        {

            case 'n':

                printf("My name is Lyong./n");

                break;

             case 'g':

                printf("Her name is Xxiong./n");

                break;

            case 'l':

                b_opt_arg = optarg;

                printf("Our love is %s/n", optarg);

                break;

            case '?':

                ec = (char)optopt;

                printf("無效的選項字元 /' %c /'!/n", ec);

                break;

            case ':':

                printf("缺少選項參數!/n");

                break;

        }

    }

    return 0;

}

測試結果:

$ ./opt_parse_demo -a

無效的選項字元 ' a '!

$ ./opt_parse_demo -l

缺少選項參數!

7、GNU提供的getopt()函數的特點

上面所設計的getopt()函數是UNIX支援小組提供的,其執行時一碰到不以'-'開始的指令行參數就停止尋找選項。而GNU提供的getopt()函數與之不同,它會掃描整個指令行來尋找選項。當調用GNU getopt()函數并處理指令行參數的時候,它重新排列argv中的元素,這樣當重排結束時,所有選項都被移動到前面并且那些繼續檢查argv [optind]至argv[argc-1]中剩餘參數的代碼仍正常工作,但在任何情況下,碰到特殊參數'--'就結束對選項的掃描。

可以輸入一個亂序的指令行,檢視opt_parse_demo的輸出:

$ ./opt_parse_demo -l forever a b c d -g -n

Our love is forever

Her name is Xxiong.

My name is Lyong.

GNU getopt()第二個特點是可以在optstring中使用特殊的首字元改變getopt()的預設行為:

optstring[0] = '+',這樣就與UNIX支援小組提供的getopt()很相近了。

optstring[0] = '-',會在optarg中得到指令行中的每個參數。

以上兩種情況下,':'可以作為第二個字元使用。

GNU getopt()第三個特點是optstring中的選項字元後面接兩個冒号,就允許該選項有可選的選項參數。在選項參數不存在的情況下,GNU getopt()傳回選項字元并将optarg設定為NULL。

8、GNU長選項指令行解析

20 世紀 90 年代,UNIX 應用程式開始支援長選項,即一對短橫線、一個描述性選項名稱,還可以包含一個使用等号連接配接到選項的參數。

GNU提供了getopt-long()和getopt-long-only()函數支援長選項的指令行解析,其中,後者的長選項字串是以一個短橫線開始的,而非一對短橫線。

getopt_long() 是同時支援長選項和短選項的 getopt() 版本。下面是它們的聲明:

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

getopt_long()的前三個參數與上面的getopt()相同,第4個參數是指向option結構的數組,option結構被稱為“長選項表”。longindex參數如果沒有設定為 NULL,那麼它就指向一個變量,這個變量會被指派為尋找到的長選項在longopts中的索引值,這可以用于錯誤診斷。

option結構在getopt.h中的聲明如下:

struct option{

    const char *name;

    int has_arg;

    int *flag;

    int val;

};

對結構中的各元素解釋如下:

const char *name

這是選項名,前面沒有短橫線。譬如"help"、"verbose"之類。

int has_arg

描述了選項是否有選項參數。如果有,是哪種類型的參數,此時,它的值一定是下表中的一個。

符号常量  數值  含義 

no_argument  0 選項沒有參數 

required_argument 1 選項需要參數

optional_argument 2 選項參數可選 

int *flag

如果這個指針為NULL,那麼 getopt_long()傳回該結構val字段中的數值。如果該指針不為NULL,getopt_long()會使得它所指向的變量中填入val字段中的數值,并且getopt_long()傳回0。如果flag不是NULL,但未發現長選項,那麼它所指向的變量的數值不變。

int val

這個值是發現了長選項時的傳回值,或者flag不是NULL時載入*flag中的值。典型情況下,若flag不是NULL,那麼val是個真/假值,譬如1或0;另一方面,如果flag是NULL,那麼 val通常是字元常量,若長選項與短選項一緻,那麼該字元常量應該與optstring中出現的這個選項的參數相同。

每個長選項在長選項表中都有一個單獨條目,該條目裡需要填入正确的數值。數組中最後的元素的值應該全是0。數組不需要排序,getopt_long()會進行線性搜尋。但是,根據長名字來排序會使程式員讀起來更容易。

以上所說的flag和val的用法看上去有點混亂,但它們很有實用價值,是以有必要搞透徹了。

大部分時候,程式員會根據getopt_long()發現的選項,在選項處理過程中要設定一些标記變量,譬如在使用getopt()時,經常做出如下的程式格式:

int do_name, do_gf_name, do_love;

char *b_opt_arg;

while((c = getopt(argc, argv, ":ngl:")) != -1)

{

    switch (c){

    case 'n':

        do_name = 1;

    case 'g':

        do_gf_name = 1;

        break;

        break;

    case 'l':

        b_opt_arg = optarg;

    ……

    }

}

當flag不為NULL時,getopt_long*()會為你設定标記變量。也就是說上面的代碼中,關于選項'n'、'l'的處理,隻是設定一些标記,如果flag不為NULL,時,getopt_long()可以自動為各選項所對應的标記變量設定标記,這樣就能夠将上面的switch語句中的兩種種情況減少到了一種。下面給出一個長選項表以及相應處理代碼的例子。

清單5:

#include <stdio.h>

#include <getopt.h>

int do_name, do_gf_name;

char *l_opt_arg;

struct option longopts[] = {

    { "name",        no_argument,            &do_name,        1    },

    { "gf_name",    no_argument,            &do_gf_name,    1    },

    { "love",        required_argument,    NULL,                'l'    },

    {     0,    0,    0,    0},

};

int main(int argc, char *argv[])

{

    int c;

    while((c = getopt_long(argc, argv, ":l:", longopts, NULL)) != -1){

        switch (c){

        case 'l':

            l_opt_arg = optarg;

            printf("Our love is %s!/n", l_opt_arg);

            break;

        case 0:

            printf("getopt_long()設定變量 : do_name = %d/n", do_name);

            printf("getopt_long()設定變量 : do_gf_name = %d/n", do_gf_name);

            break;

        }

    }

    return 0;

}

在進行測試之前,再來回顧一下有關option結構中的指針flag的說明吧。

如果這個指針為NULL,那麼 getopt_long()傳回該結構val字段中的數值。如果該指針不為NULL,getopt_long()會使得它所指向的變量中填入val字段中的數值,并且getopt_long()傳回0。如果flag不是NULL,但未發現長選項,那麼它所指向的變量的數值不變。

下面測試一下:

$ ./long_opt_demo --name

getopt_long()設定變量 : do_name = 1

getopt_long()設定變量 : do_gf_name = 0

$ ./long_opt_demo --gf_name

getopt_long()設定變量 : do_name = 0

getopt_long()設定變量 : do_gf_name = 1

$ ./long_opt_demo --love forever

Our love is forever!

$ ./long_opt_demo -l forever

Our love is forever!

測試過後,應該有所感觸了。關于flag和val的讨論到此為止。下面總結一下get_long()的各種傳回值的含義:

傳回值   

 含 義

 getopt_long()設定一個标志,它的值與option結構中的val字段的值一樣

1

 每碰到一個指令行參數,optarg都會記錄它

'?'

 無效選項

':'

 缺少選項參數

'x'

 選項字元'x'

-1

 選項解析結束

從實用的角度來說,我們更期望每個長選項都對應一個短選項,這種情況下,在option結構中,隻要将flag設定為NULL,并将val設定為長選項所對應的短選項字元即可。譬如上面清單5中的程式,修改如下。

清單6:

#include <stdio.h>

#include <getopt.h>

int do_name, do_gf_name;

char *l_opt_arg;

struct option longopts[] = {

    { "name",        no_argument,            NULL,                'n'    },

    { "gf_name",    no_argument,            NULL,                'g'    },

    { "love",        required_argument,    NULL,                'l'    },

    {     0,    0,    0,    0},

};

int main(int argc, char *argv[])

{

    int c;

    while((c = getopt_long(argc, argv, ":l:", longopts, NULL)) != -1){

        switch (c){

        case 'n':

            printf("My name is LYR./n");

            break;

        case 'g':

            printf("Her name is BX./n");

            break;

        case 'l':

            l_opt_arg = optarg;

            printf("Our love is %s!/n", l_opt_arg);

            break;

        }

    }

    return 0;

}

測試結果如下:

$ ./long_opt_demo --name --gf_name --love forever

My name is LYR.

Her name is BX.

Our love is forever!

$ ./long_opt_demo -ng -l forever

My name is LYR.

Her name is BX.

Our love is forever!

9、在LINUX之外的系統平台上使用GNU getopt()或getopt_long()

隻要從GNU程式或GNU C Library(GLIBC)的CVS檔案檔案中copy源檔案即可(http://sourceware.org/glibc/)。所需源檔案是 getopt.h、getopt.c和getoptl.c,将這些檔案包含在你的項目中。另外,你的項目中最好也将COPYING.LIB檔案包含進去,因為GNU LGPL(GNU 程式庫公共許可證)的内容全部包括在命名為COPYING.LIB 的檔案中。

10、結論

程式需要能夠快速處理各個選項和參數,且要求不會浪費開發人員的太多時間。在這一點上,無論是GUI(圖形使用者互動)程式還是CUI(指令行互動)程式,都是其首要任務,其差別僅在于實作方式的不同。GUI通過菜單、對話框之類的圖形控件來完成互動,而CUI使用了純文字的互動方式。在程式開發中,許多測試程式用CUI來完成是首選方案。

getopt() 函數是一個标準庫調用,可允許您使用直接的 while/switch 語句友善地逐個處理指令行參數和檢測選項(帶或不帶附加的參數)。與其類似的 getopt_long() 允許在幾乎不進行額外工作的情況下處理更具描述性的長選項,這非常受開發人員的歡迎。

 原文位址 http://www.1to2.us/Linux-getopt-a118887.htm

繼續閱讀