簡介: 所有 UNIX® 程序甚至那些具有圖形用戶界面(graphical user interface,GUI)的程序,都能接受和處理命令行選項(xiàng)。對于某些程序,這是與其他程序或用戶進(jìn)行交互的主要手段。具有可靠的復(fù)雜命令行參數(shù)處理機(jī)制,會使得您的應(yīng)用程序更好、更有用。不過很多開發(fā)人員都將其寶貴的時(shí)間花在了編寫自己的命令行解析器,卻不使用 getopt()
,而后者是一個(gè)專門設(shè)計(jì)來減輕命令行處理負(fù)擔(dān)的庫函數(shù)。請閱讀本文,以了解如何讓 getopt()
在全局結(jié)構(gòu)中記錄命令參數(shù),以便隨后隨時(shí)在整個(gè)程序中使用。
在海晏等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強(qiáng)發(fā)展的系統(tǒng)性、市場前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務(wù)理念,為客戶提供做網(wǎng)站、成都做網(wǎng)站 網(wǎng)站設(shè)計(jì)制作按需設(shè)計(jì)網(wǎng)站,公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),成都品牌網(wǎng)站建設(shè),營銷型網(wǎng)站建設(shè),成都外貿(mào)網(wǎng)站建設(shè),海晏網(wǎng)站建設(shè)費(fèi)用合理。
引言
在早期的 UNIX® 中,其命令行環(huán)境(當(dāng)時(shí)的唯一用戶界面)包含著數(shù)十種小的文本處理工具。這些工具非常小,通??珊芎玫赝瓿梢豁?xiàng)工作。這些工具通過較長的命令管道鏈接在一起,前面的程序?qū)⑵漭敵鰝鬟f給下一個(gè)程序以作為輸入,整個(gè)過程由各種命令行選項(xiàng)和參數(shù)加以控制。
正是 UNIX 的這方面的特征使其成為了極為強(qiáng)大的處理基于本文的數(shù)據(jù)的環(huán)境,而這也是其在公司環(huán)境中的最初用途之一。在命令管道的一端輸入一些文本,然后在另一端檢索經(jīng)過處理的輸出。
命令行選項(xiàng)和參數(shù)控制 UNIX 程序,告知它們?nèi)绾蝿幼鳌W鳛殚_發(fā)人員,您要負(fù)責(zé)從傳遞給您程序的 main()
函數(shù)的命令行發(fā)現(xiàn)用戶的意圖。本文將演示如何使用標(biāo)準(zhǔn) getopt()
和 getopt_long()
函數(shù)來簡化命令行處理工作,并討論了一項(xiàng)用于跟蹤命令行選項(xiàng)的技術(shù)。
開始之前
本文包含的示例代碼(請參見下載)是使用 C 開發(fā)工具(C Development Tooling,CDT)在 Eclipse 3.1 中編寫的;getopt_demo和 getopt_long_demo 項(xiàng)目是 Managed Make 項(xiàng)目,均使用 CDT 的程序生成規(guī)則構(gòu)建。在項(xiàng)目中沒有包含 Makefile,如果需要在 Eclipse 外編譯代碼,可以自己方便地生成一個(gè)。
如果尚未嘗試過 Eclipse(請參閱參考資料),真的應(yīng)該嘗試一下——這是一個(gè)優(yōu)秀的集成開發(fā)環(huán)境(integrated development environment,IDE),其每個(gè)新版本都有較大的提升。這是來自“強(qiáng)硬派” EMACS 和 Makefile 開發(fā)人員的作品。
命令行
在編寫新程序時(shí),首先遇到的障礙之一就是如何處理控制其行為的命令行參數(shù)。這包括從命令行傳遞給您程序的 main()
函數(shù)的一個(gè)整數(shù)計(jì)數(shù)(通常名為 argc)和一個(gè)指向字符串的指針數(shù)組(通常名為 argv).可以采用兩種實(shí)質(zhì)一樣的方式聲明標(biāo)注 main()
函數(shù),如清單 1 中所示。
清單 1. 聲明 main()
函數(shù)的兩種方式
int main( int argc, char *argv[] ); int main( int argc, char **argv ); |
第一種方式使用的是指向 char
指針數(shù)組,現(xiàn)在似乎很流行這種方式,比第二種方式(其指針指向多個(gè)指向 char
的指針)略微清楚一些。由于某些原因,我使用第二種方式的時(shí)間更多一些,這可能源于我在高中時(shí)艱難學(xué)習(xí) C 指針的經(jīng)歷。對于所有的用途和目的,這兩種方法都是一樣的,因此可以使用其中您自己最喜歡的方式。
當(dāng) C 運(yùn)行時(shí)庫的程序啟動代碼調(diào)用您的 main()
時(shí),已經(jīng)對命令行進(jìn)行了處理。argc
參數(shù)包含參數(shù)的計(jì)數(shù)值,而 argv
包含指向這些參數(shù)的指針數(shù)組。對于 C 運(yùn)行時(shí)庫,arguments 是程序的名稱,程序名后的任何內(nèi)容都應(yīng)該使用空格加以分隔。
例如,如果使用參數(shù) -v bar www.ibm.com
運(yùn)行一個(gè)名為 foo 程序,您的 argc 將設(shè)置為 4,argv
的設(shè)置情況將如清單 2 中所示。
清單 2. argv 的內(nèi)容
argv[0] - foo argv[1] - -v argv[2] - bar argv[3] - www.ibm.com |
一個(gè)程序僅有一組命令行參數(shù),因此我要將此信息存儲在記錄選項(xiàng)和設(shè)置的全局結(jié)構(gòu)中。對程序有意義的要跟蹤的任何內(nèi)容都可以記錄到此結(jié)構(gòu)中,我將使用結(jié)構(gòu)來幫助減少全局變量的數(shù)量。正如我在網(wǎng)絡(luò)服務(wù)設(shè)計(jì)文章(請參閱參考資料)所提到的,全局變量非常不適合用于線程化編程中,因此要謹(jǐn)慎使用。
示例代碼將演示一個(gè)假想的 doc2html 程序的命令行處理。該 doc2html 程序?qū)⒛撤N類型的文檔轉(zhuǎn)換為 HTML,具體由用戶指定的命令行選項(xiàng)控制。它支持以下選項(xiàng):
-I
——不創(chuàng)建關(guān)鍵字索引。-l lang
——轉(zhuǎn)換為使用語言代碼 lang
指定的語言。-o outfile.html
——將經(jīng)過轉(zhuǎn)換的文檔寫入到 outfile.html,而不是打印到標(biāo)準(zhǔn)輸出。-v
——進(jìn)行轉(zhuǎn)換時(shí)提供詳細(xì)信息;可以多次指定,以提高診斷級別。您還將支持 -h
和 -?
,以打印幫助消息來提示各個(gè)選項(xiàng)的用途。
簡單命令行處理: getopt()
getopt()
函數(shù)位于 unistd.h 系統(tǒng)頭文件中,其原型如清單 3 中所示:
清單 3. getopt()
原型
int getopt( int argc, char *const argv[], const char *optstring ); |
給定了命令參數(shù)的數(shù)量 (argc
)、指向這些參數(shù)的數(shù)組 (argv
) 和選項(xiàng)字符串 (optstring
) 后,getopt()
將返回第一個(gè)選項(xiàng),并設(shè)置一些全局變量。使用相同的參數(shù)再次調(diào)用該函數(shù)時(shí),它將返回下一個(gè)選項(xiàng),并設(shè)置相應(yīng)的全局變量。如果不再有識別到的選項(xiàng),將返回-1
,此任務(wù)就完成了。
getopt()
所設(shè)置的全局變量包括:
optarg
——指向當(dāng)前選項(xiàng)參數(shù)(如果有)的指針。optind
——再次調(diào)用 getopt()
時(shí)的下一個(gè) argv 指針的索引。optopt
——最后一個(gè)已知選項(xiàng)。對于每個(gè)選項(xiàng),選項(xiàng)字符串 (optstring
) 中都包含一個(gè)對應(yīng)的字符。具有參數(shù)的選項(xiàng)(如示例中的 -l
和 -o
選項(xiàng))后面跟有一個(gè) :
字符。示例所使用的 optstring
為 Il:o:vh?
(前面提到,還要支持最后兩個(gè)用于打印程序的使用方法消息的選項(xiàng))。
可以重復(fù)調(diào)用 getopt()
,直到其返回 -1
為止;任何剩下的命令行參數(shù)通常視為文件名或程序相應(yīng)的其他內(nèi)容。
getopt()
的使用
讓我們對 getopt_demo 項(xiàng)目的代碼進(jìn)行一下深入分析;為了方便起見,我在此處將此代碼拆分為多個(gè)部分,但您可以在可下載源代碼部分獲得完整的代碼(請參見下載)。
在清單 4 中,可以看到系統(tǒng)演示程序所使用的系統(tǒng)頭文件;標(biāo)準(zhǔn) stdio.h
提供標(biāo)準(zhǔn) I/O 函數(shù)原型,stdlib.h
提供 EXIT_SUCCESS
和EXIT_FAILURE
,unistd.h
提供 getopt()
。
清單 4. 系統(tǒng)頭文件
#include |
清單 5 顯示了我所創(chuàng)建的 globalArgs
結(jié)構(gòu),用于以合理的方式存儲命令行選項(xiàng)。由于這是個(gè)全局變量,程序中任何位置的代碼都可以訪問這些變量,以確定是否創(chuàng)建關(guān)鍵字索引、生成何種語言等等事項(xiàng)。最好讓 main()
函數(shù)外的代碼將此結(jié)構(gòu)視為一個(gè)常量、只讀存儲區(qū),因?yàn)槌绦虻娜魏尾糠侄伎梢砸蕾囉谄鋬?nèi)容。
每個(gè)命令行選擇都有一個(gè)對應(yīng)的選項(xiàng),而其他變量用于存儲輸出文件名、指向輸入文件列表的指針和輸入文件數(shù)量。
清單 5. 全局參數(shù)存儲和選項(xiàng)字符串
struct globalArgs_t { int noIndex; /* -I option */ char *langCode; /* -l option */ const char *outFileName; /* -o option */ FILE *outFile; int verbosity; /* -v option */ char **inputFiles; /* input files */ int numInputFiles; /* # of input files */ } globalArgs; static const char *optString = "Il:o:vh?"; |
選項(xiàng)字符串 optString
告知 getopt()
可以處理哪個(gè)選項(xiàng)以及哪個(gè)選項(xiàng)需要參數(shù)。如果在處期間遇到了其他選項(xiàng),getopt()
將顯示一個(gè)錯誤消息,程序?qū)⒃陲@示了使用方法消息后退出。
下面的清單 6 包含一些從 main()
引用的用法消息函數(shù)和文檔轉(zhuǎn)換函數(shù)的小存根??梢詫@些存根進(jìn)行自由更改,以用于更為有用的目的。
清單 6. 存根
void display_usage( void ) { puts( "doc2html - convert documents to HTML" ); /* ... */ exit( EXIT_FAILURE ); } void convert_document( void ) { /* ... */ } |
最后,如清單 7 中所示,在 main()
函數(shù)中使用此結(jié)構(gòu)。和優(yōu)秀的開發(fā)人員一樣,您需要首先初始化 globalArgs
結(jié)構(gòu),然后才開始處理命令行參數(shù)。在您的程序中,可以借此設(shè)置在一定情況下合理的缺省值,以便在以后有更合適的缺省值時(shí)更方便地對其進(jìn)行調(diào)整。
清單 7. 初始化
int main( int argc, char *argv[] ) { int opt = 0; /* Initialize globalArgs before we get to work. */ globalArgs.noIndex = 0; /* false */ globalArgs.langCode = NULL; globalArgs.outFileName = NULL; globalArgs.outFile = NULL; globalArgs.verbosity = 0; globalArgs.inputFiles = NULL; globalArgs.numInputFiles = 0; |
清單 8 中的 while
循環(huán)和 switch
語句是用于本程序的命令行處理的代碼部分。只要 getopt()
發(fā)現(xiàn)選項(xiàng),switch
語句將確定找到的是哪個(gè)選項(xiàng),將能在 globalArgs
結(jié)構(gòu)中看到具體情況。當(dāng) getopt()
最終返回 -1
時(shí),就完成了選項(xiàng)處理過程,剩下的都是您的輸入文件了。
清單 8. 使用 getopt()
處理 argc/argv
opt = getopt( argc, argv, optString ); while( opt != -1 ) { switch( opt ) { case 'I': globalArgs.noIndex = 1; /* true */ break; case 'l': globalArgs.langCode = optarg; break; case 'o': globalArgs.outFileName = optarg; break; case 'v': globalArgs.verbosity++; break; case 'h': /* fall-through is intentional */ case '?': display_usage(); break; default: /* You won't actually get here. */ break; } opt = getopt( argc, argv, optString ); } globalArgs.inputFiles = argv + optind; globalArgs.numInputFiles = argc - optind; |
既然已經(jīng)完成了參數(shù)和選項(xiàng)的收集工作,接下來就可以執(zhí)行程序所設(shè)計(jì)的任何功能(在本例中是進(jìn)行文檔轉(zhuǎn)換),然后退出(清單 9)。
清單 9. 開始工作
convert_document(); return EXIT_SUCCESS; } |
好,工作完成,非常漂亮?,F(xiàn)在就可以不再往下讀了。不過,如果您希望程序符合 90 年代末期的標(biāo)準(zhǔn)并支持 GNU 應(yīng)用程序中流行的長 選項(xiàng),則請繼續(xù)關(guān)注下面的內(nèi)容。
復(fù)雜命令行處理: getopt_long()
在 20 世紀(jì) 90 年代(如果沒有記錯的話),UNIX 應(yīng)用程序開始支持長選項(xiàng),即一對短橫線(而不是普通短 選項(xiàng)所使用的單個(gè)短橫線)、一個(gè)描述性選項(xiàng)名稱還可以包含一個(gè)使用等號連接到選項(xiàng)的參數(shù)。
幸運(yùn)的是,可以通過使用 getopt_long()
向程序添加長選項(xiàng)支持。您可能已經(jīng)猜到了,getopt_long()
是同時(shí)支持長選項(xiàng)和短選項(xiàng)的getopt()
版本。
getopt_long()
函數(shù)還接受其他參數(shù),其中一個(gè)是指向 struct option
對象數(shù)組的指針。此結(jié)構(gòu)相當(dāng)直接,如清單 10 中所示。
清單 10. getopt_long()
的選項(xiàng)
struct option { char *name; int has_arg; int *flag; int val; }; |
name
成員是指向長選項(xiàng)名稱(帶兩個(gè)短橫線)的指針。has_arg
成員設(shè)置為 no_argument
、optional_argument
, 或required_argument
(均在 getopt.h
中定義)之一,以指示選項(xiàng)是否具有參數(shù)。如果 flag 成員未設(shè)置為 NULL,在處理期間遇到此選項(xiàng)時(shí),會使用 val
成員的值填充它所指向的 int
值。如果 flag 成員為 NULL
,在 getopt_long()
遇到此選項(xiàng)時(shí),將返回 val
中的值;通過將 val
設(shè)置為選項(xiàng)的 short
參數(shù),可以在不添加任何其他代碼的情況下使用 getopt_long()
——處理 while loop
和 switch
的現(xiàn)有 getopt()
將自動處理此選項(xiàng)。
這已經(jīng)變得更為靈活了,因?yàn)楦鱾€(gè)選項(xiàng)現(xiàn)在可以具有可選參數(shù)了。更重要的是,僅需要進(jìn)行很少的工作,就可以方便地放入現(xiàn)有代碼中。
讓我們看看如何使用 getopt_long()
來對示例程序進(jìn)行更改(getopt_long_demo 項(xiàng)目可從下載部分獲得)。
使用 getopt_long()
由于 getopt_long_demo 幾乎與剛剛討論的 getopt_demo 代碼一樣,因此我將僅對更改的代碼進(jìn)行說明。由于現(xiàn)在已經(jīng)有了更大的靈活性,因此還將添加對 --randomize
選項(xiàng)(沒有對應(yīng)的短選項(xiàng))的支持。
getopt_long()
函數(shù)在 getopt.h
頭文件(而非 unistd.h
)中,因此將需要將該頭文件包含進(jìn)來(請參見清單 11)。我還包含了string.h
,因?yàn)閷⑸院笫褂?nbsp;strcmp()
來幫助確定處理的是哪個(gè)長參數(shù)。
清單 11. 其他頭文件
#include |
您已經(jīng)為 --randomize
選項(xiàng)在 globalArgs
中添加了一個(gè)標(biāo)志(請參見清單 12),并創(chuàng)建了 longOpts
數(shù)組來存儲關(guān)于此程序支持的長選項(xiàng)的信息。除了 --randomize
外,所有的參數(shù)都與現(xiàn)有短選項(xiàng)對應(yīng)(例如,--no-index
等同于 -I
)。通過在選項(xiàng)結(jié)構(gòu)中包含其短選項(xiàng)等效項(xiàng),可以在不向程序添加任何其他代碼的情況下處理等效的長選項(xiàng)。
清單 12. 擴(kuò)展后的參數(shù)
struct globalArgs_t { int noIndex; /* -I option */ char *langCode; /* -l option */ const char *outFileName; /* -o option */ FILE *outFile; int verbosity; /* -v option */ char **inputFiles; /* input files */ int numInputFiles; /* # of input files */ int randomized; /* --randomize option */ } globalArgs; static const char *optString = "Il:o:vh?"; static const struct option longOpts[] = { { "no-index", no_argument, NULL, 'I' }, { "language", required_argument, NULL, 'l' }, { "output", required_argument, NULL, 'o' }, { "verbose", no_argument, NULL, 'v' }, { "randomize", no_argument, NULL, 0 }, { "help", no_argument, NULL, 'h' }, { NULL, no_argument, NULL, 0 } }; |
清單 13 將 getop()
調(diào)用更改為了 getopt_long()
,除了 getopt()
的參數(shù)外,它還接受 longOpts
數(shù)組和 int
指針 (longIndex
)。當(dāng)getopt_long()
返回 0
時(shí),longIndex
所指向的整數(shù)將設(shè)置為當(dāng)前找到的長選項(xiàng)的索引。
清單 13. 新的經(jīng)改進(jìn)的選項(xiàng)處理
opt = getopt_long( argc, argv, optString, longOpts, &longIndex ); while( opt != -1 ) { switch( opt ) { case 'I': globalArgs.noIndex = 1; /* true */ break; case 'l': globalArgs.langCode = optarg; break; case 'o': globalArgs.outFileName = optarg; break; case 'v': globalArgs.verbosity++; break; case 'h': /* fall-through is intentional */ case '?': display_usage(); break; case 0: /* long option without a short arg */ if( strcmp( "randomize", longOpts[longIndex].name ) == 0 ) { globalArgs.randomized = 1; } break; default: /* You won't actually get here. */ break; } opt = getopt_long( argc, argv, optString, longOpts, amp;longIndex ); } |
我還添加了 0
的 case,以便處理任何不與現(xiàn)有短選項(xiàng)匹配的長選項(xiàng)。在此例中,只有一個(gè)長選項(xiàng),但代碼仍然使用 strcmp()
來確保它是預(yù)期的那個(gè)選項(xiàng)。
這樣就全部搞定了;程序現(xiàn)在支持更為詳細(xì)(對臨時(shí)用戶更加友好)的長選項(xiàng)。
總結(jié)
UNIX 用戶始終依賴于命令行參數(shù)來修改程序的行為,特別是那些設(shè)計(jì)作為小工具集合 (UNIX 外殼環(huán)境)的一部分使用的實(shí)用工具更是如此。程序需要能夠快速處理各個(gè)選項(xiàng)和參數(shù),且要求不會浪費(fèi)開發(fā)人員的太多時(shí)間。畢竟,幾乎沒有程序設(shè)計(jì)為僅處理命令行參數(shù),開發(fā)人員更應(yīng)該將精力放在程序所實(shí)際進(jìn)行的工作上。
getopt()
函數(shù)是一個(gè)標(biāo)準(zhǔn)庫調(diào)用,可允許您使用直接的 while/switch 語句方便地逐個(gè)處理命令行參數(shù)和檢測選項(xiàng)(帶或不帶附加的參數(shù))。與其類似的 getopt_long()
允許在幾乎不進(jìn)行額外工作的情況下處理更具描述性的長選項(xiàng),這非常受開發(fā)人員的歡迎。
既然已經(jīng)知道了如何方便地處理命令行選項(xiàng),現(xiàn)在就可以集中精力改進(jìn)您的程序的命令行,可以添加長選項(xiàng)支持,或添加之前由于不想向程序添加額外的命令行選項(xiàng)處理而擱置的任何其他選項(xiàng)。
不要忘記在某處記錄您所有的選項(xiàng)和參數(shù),并提供某種類型的內(nèi)置幫助函數(shù)來為健忘的用戶提供幫助。
回頁首
下載
描述 | 名字 | 大小 | 下載方法 |
---|---|---|---|
Sample getopt() program | au-getopt_demo.zip | 23KB | HTTP |
Sample getopt_long() program | au-getopt_long_demo.zip | 24KB | HTTP |
關(guān)于下載方法的信息