Linux程式設計——用getopt處理命令列引數

helloxchen發表於2010-11-05

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 ----&gtfoo -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

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 fputs(argv[i], stdout);
if(i < argc-1)
putchar(' ');
}
if(nflg == 0)
putchar('n');

return 0;
}

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

5、命令列引數解析函式 —— getopt()
getopt()函式宣告如下:

#include

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
#include

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 %sn", 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
#include

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 %sn", 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
#include

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 %sn", 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

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
#include

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 = %dn", do_name);
printf("getopt_long()設定變數 : do_gf_name = %dn", 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()的各種返回值的含義:

返回值
含 義

0
getopt_long()設定一個標誌,它的值與option結構中的val欄位的值一樣

1
每碰到一個命令列引數,optarg都會記錄它

'?'
無效選項

':'
缺少選項引數

'x'
選項字元'x'

-1
選項解析結束

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

清單6:
#include
#include

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原始檔即可()。所需原始檔是 getopt.h、getopt.c和getoptl.c,將這些檔案包含在你的專案中。另外,你的專案中最好也將COPYING.LIB檔案包含進去,因為GNU LGPL(GNU 程式庫公共許可證)的內容全部包括在命名為COPYING.LIB 的檔案中。
10、結論

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

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


本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/lixingjian4/archive/2009/09/05/4523500.aspx

[@more@]

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/24790158/viewspace-1040985/,如需轉載,請註明出處,否則將追究法律責任。

相關文章