溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

使用 getopt() 、getopt_long()、getopt_long_only()進(jìn)行命令行處理

發(fā)布時(shí)間:2020-07-09 03:11:06 來源:網(wǎng)絡(luò) 閱讀:1757 作者:驛落黃昏 欄目:系統(tǒng)運(yùn)維

 簡介: 所有 UNIX® 程序甚至那些具有圖形用戶界面(graphical user interface,GUI)的程序,都能接受和處理命令行選項(xiàng)。對于某些程序,這是與其他程序或用戶進(jìn)行交互的主要手段。具有可靠的復(fù)雜命令行參數(shù)處理機(jī)制,會(huì)使得您的應(yīng)用程序更好、更有用。不過很多開發(fā)人員都將其寶貴的時(shí)間花在了編寫自己的命令行解析器,卻不使用 getopt(),而后者是一個(gè)專門設(shè)計(jì)來減輕命令行處理負(fù)擔(dān)的庫函數(shù)。請閱讀本文,以了解如何讓 getopt() 在全局結(jié)構(gòu)中記錄命令參數(shù),以便隨后隨時(shí)在整個(gè)程序中使用。

 

 

引言

在早期的 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)绾蝿?dòng)作。作為開發(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í)庫的程序啟動(dòng)代碼調(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ù),因此我要將此信息存儲(chǔ)在記錄選項(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)的全局變量。如果不再有識(shí)別到的選項(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_SUCCESSEXIT_FAILURE,unistd.h 提供 getopt()


清單 4. 系統(tǒng)頭文件 

                
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

 

清單 5 顯示了我所創(chuàng)建的 globalArgs 結(jié)構(gòu),用于以合理的方式存儲(chǔ)命令行選項(xiàng)。由于這是個(gè)全局變量,程序中任何位置的代碼都可以訪問這些變量,以確定是否創(chuàng)建關(guān)鍵字索引、生成何種語言等等事項(xiàng)。最好讓 main() 函數(shù)外的代碼將此結(jié)構(gòu)視為一個(gè)常量、只讀存儲(chǔ)區(qū),因?yàn)槌绦虻娜魏尾糠侄伎梢砸蕾囉谄鋬?nèi)容。

每個(gè)命令行選擇都有一個(gè)對應(yīng)的選項(xiàng),而其他變量用于存儲(chǔ)輸出文件名、指向輸入文件列表的指針和輸入文件數(shù)量。


清單 5. 全局參數(shù)存儲(chǔ)和選項(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è)錯(cuò)誤消息,程序?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;
}

 

好,工作完成,非常漂亮。現(xiàn)在就可以不再往下讀了。不過,如果您希望程序符合 90 年代末期的標(biāo)準(zhǔn)并支持 GNU 應(yīng)用程序中流行的 選項(xiàng),則請繼續(xù)關(guān)注下面的內(nèi)容。

復(fù)雜命令行處理: getopt_long()

在 20 世紀(jì) 90 年代(如果沒有記錯(cuò)的話),UNIX 應(yīng)用程序開始支持長選項(xiàng),即一對短橫線(而不是普通 選項(xiàng)所使用的單個(gè)短橫線)、一個(gè)描述性選項(xiàng)名稱還可以包含一個(gè)使用等號(hào)連接到選項(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í),會(huì)使用 val 成員的值填充它所指向的 int 值。如果 flag 成員為 NULL,在 getopt_long() 遇到此選項(xiàng)時(shí),將返回 val 中的值;通過將 val 設(shè)置為選項(xiàng)的 short 參數(shù),可以在不添加任何其他代碼的情況下使用 getopt_long()——處理 while loop 和 switch 的現(xiàn)有 getopt() 將自動(dòng)處理此選項(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 <getopt.h>
#include <string.h>

 

您已經(jīng)為 --randomize 選項(xiàng)在 globalArgs 中添加了一個(gè)標(biāo)志(請參見清單 12),并創(chuàng)建了 longOpts 數(shù)組來存儲(chǔ)關(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ù),且要求不會(huì)浪費(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)于下載方法的信息

 

 

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI