Linux程序設計——用getopt處理命令行參數

原來命令行參數處理能夠這麼寫-getopt?html

常常要寫點Linux下命令行的測試程序,都須要對命令行參數作一些處理,之前都是本身來寫參數的處理,不僅每次寫的都不同,並且每次還浪費時間去作參數的判斷,並且寫出來的代碼很難看,最近在看老大寫的一個測試程序,發覺裏面用到了getopt或getopt_long這個函數來處理,不僅代碼清晰,並且很是容易的就能實現人們經常使用的那種短橫線帶參數的命令行參數處理模式,在網上查了一些資料,發現介紹仍是不少,特別介紹出來給你們,讓你們再也不糾結於命令行的處理,而更多的去專一功能的實現。程序員

下面這篇文章講的比較詳細:轉自http://blogold.chinaunix.net/u/7040/showart_244389.html數組

《Linux程序設計——用getopt處理命令行參數》函數

Linux下不少程序甚至那些具備圖形用戶界面(graphical user interface,GUI)的程序,都能接受和處理命令行選項。對於某些程序,這是與用戶進行交互的主要手段。具備可靠的複雜命令行參數處理機制,會使得您的應用程序更好、更有用。getopt()是一個專門設計來減輕命令行處理負擔的庫函數。
一、命令行參數
命令行程序設計的首要任務是解析命令行參數,GUI派的程序員不多關心這個。這裏,對參數採用了一種比較通俗的定義:命令行上除命令名以外的字符串。參數由多項構成,項與項之間用空白符彼此隔開。
參數進一步分爲選項操做數。選項用於修改程序的默認行爲或爲程序提供信息,比較老的約定是以短劃線開頭。選項後能夠跟隨一些參數,稱爲選項參數。剩下的就是操做數了。
二、POSIX約定 POSIX表示可移植操做系統接口: Portable Operating System Interface,電氣和電子工程師協會(Institute of Electrical and Electronics Engineers,IEEE)最初開發 POSIX 標準,是爲了提升 UNIX 環境下應用程序的可移植性。然而,POSIX 並不侷限於 UNIX。許多其它的操做系統,例如 DEC OpenVMS 和 Microsoft Windows NT,都支持 POSIX 標準。工具

下面是POSIX標準中關於程序名、參數的約定:測試

  1. 程序名不宜少於2個字符且很少於9個字符
  2. 程序名應只包含小寫字母和阿拉伯數字
  3. 選項名應該是單字符或者單數字,且以短橫‘-’爲前
  4. 多個不須要選項參數的選項,能夠合併。(譬如:foo -a -b -c ---->foo -abc)
  5. 選項與其參數之間用空白符隔開
  6. 選項參數不可選
  7. 若選項參數有多值,要將其併爲一個字串傳進來。譬如:myprog -u "arnold,joe,jane"。這種狀況下,須要本身解決這些參數的分離問題
  8. 選項應該在操做數出現以前出現
  9. 特殊參數‘--'指明全部參數都結束了,其後任何參數都認爲是操做數
  10. 選項如何排列沒有什麼關係,但對互相排斥的選項,若是一個選項的操做結果覆蓋其餘選項的操做結果時,最後一個選項起做用;若是選項重複,則順序處理
  11. 容許操做數的順序影響程序行爲,但須要做文檔說明
  12. 讀寫指定文件的程序應該將單個參數'-'做爲有意義的標準輸入或輸出來對待

三、GNU長選項ui

GNU鼓勵程序員使用--help、--verbose等形式的長選項。這些選項不只不與POSIX約定衝突,並且容易記憶,另外也提供了在全部GNU工具之間保持一致性的機會。GNU長選項有本身的約定:spa

  1. 對於已經遵循POSIX約定的GNU程序,每一個短選項都有一個對應的長選項
  2. 額外針對GNU的長選項不須要對應的短選項,僅僅推薦要有
  3. 長選項能夠縮寫成保持唯一性的最短的字串
  4. 選項參數與長選項之間或經過空白字符或者經過一個'='來分隔
  5. 選項參數是可選的(只對短選項有效)
  6. 長選項容許以一個短橫線爲前綴

四、基本的命令行處理技術操作系統

C程序經過argc和argv參數訪問它的命令行參數。argc是整型數,表示參數的個數(包括命令名)。main()函數的定義方式有兩種,區別僅在於argv如何定義:.net

  
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的代碼變得更加容易了。
五、命令行參數解析函數 —— 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

六、改變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()函數的出錯信息輸出行爲:

  1. 在調用getopt()以前,將opterr設置爲0,這樣就能夠在getopt()函數發現錯誤的時候強制它不輸出任何消息。
  2. 若是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 缺乏選項參數!

七、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。
八、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()的各類返回值的含義:

  
返回值 含 義 0 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!

九、在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 的文件中。

十、結論

程序須要可以快速處理各個選項和參數,且要求不會浪費開發人員的太多時間。在這一點上,不管是GUI(圖形用戶交互)程序仍是CUI(命令行交互)程序,都是其首要任務,其區別僅在於實現方式的不一樣。GUI經過菜單、對話框之類的圖形控件來完成交互,而CUI使用了純文本的交互方式。在程序開發中,許多測試程序用CUI來完成是首選方案。

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

相關文章
相關標籤/搜索