歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux服務器 >> 用getopt處理命令行參數

用getopt處理命令行參數

日期:2017/3/2 16:38:57   编辑:Linux服務器

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()命令參數的數量 (arg

c)、指向這些參數的數組 (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()的各種返回值的含義:

  返回值

  含 義

  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!

  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() 允許在幾乎不進行額外工作的情況下處理更具描述性的長選項,這非常受開發人員的歡迎

Copyright © Linux教程網 All Rights Reserved