刺蝟@http://blog.csdn.net/littlehedgehog
[讀取命令行參數]
開始正兒八經地分析源碼了. 從哪裏入手呢,當然還是按照書上的來,我們先來看看Clamd的程序如何從生到死,Clamd是殺毒軟件的服務端,我們在客戶端提交要查殺的信息,然後Clamd服務端進行按照我們指令辦事,然後把結果返回給我們。這個貌似是unix程序的一個經典構架了,所謂的客戶端直接跟用戶打交道,然後解析用戶輸入,把真正要做的工作再傳給服務端完成。服務端其實是一個無名英雄了。 話說回來這裏第一步我們就要處理命令行下瑣碎的各個參數選項.爲了讓Clamd服務端按照我們要求來運行,這貌似是個相當麻煩的工作,比女人都麻煩,呵呵. 不過還好網上有不少linux fans 都寫過相應的博文了. 還是先來看源碼吧: [./clamd/options.c]
- /* 這個是clamd的主函數 文件名爲options,那麼這個主函數就主要在處理這個選項(參數選項)問題了*/
- int main(int argc, char **argv)
- {
- int ret, opt_index, i, len;
- struct optstruct *opt;
- const char *getopt_parameters = "hc:V";
- static struct option long_options[] =
- {
- {"help", 0, 0, 'h'},
- {"config-file", 1, 0, 'c'},
- {"version", 0, 0, 'V'},
- {"debug", 0, 0, 0},
- {0, 0, 0, 0}
- };
- #if defined(C_LINUX) && defined(CL_DEBUG)
- /* [email protected]: create a dump if needed */
- struct rlimit rlim;
- rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
- if (setrlimit(RLIMIT_CORE, &rlim) < 0)
- perror("setrlimit");
- #endif
- opt=(struct optstruct*)mcalloc(1, sizeof(struct optstruct));
- opt->optlist = NULL;
- opt->filename = NULL;
- while (1)
- {
- opt_index=0;
- ret=getopt_long(argc, argv, getopt_parameters, long_options, &opt_index);
- if (ret == -1)
- break;
- switch (ret)
- {
- case 0:
- register_long_option(opt, long_options[opt_index].name);
- break;
- default:
- if (strchr(getopt_parameters, ret))
- register_char_option(opt, ret);
- else
- {
- fprintf(stderr, "ERROR: Unknown option passed./n");
- free_opt(opt);
- exit(40);
- }
- }
- }
- if (optind < argc)
- {
- len=0;
- /* count length of non-optin arguments 下面是處理非選項參數*/
- for (i=optind; i<argc; i++)
- len+=strlen(argv[i]);
- len=len+argc-optind-1; /* add spaces between arguments */
- opt->filename=(char*)mcalloc(len + 256, sizeof(char));
- for (i=optind; i<argc; i++)
- {
- strncat(opt->filename, argv[i], strlen(argv[i]));
- if (i != argc-1)
- strncat(opt->filename, " ", 1);
- }
- }
- clamd(opt);
- free_opt(opt);
- cli_dbgmsg("exit main()");
- return(0);
- }
代碼不長,邏輯也不復雜. 不過在理解GNU提供給我們截獲參數選項操作之前,我們確實應該先來明確一下處理的關鍵點在哪裏.比如說有如下命令行 command -h 這是我們來獲取command使用幫助的時候鍵入的命令. 也就是說按照約定,我們鍵入-h 就是表明要顯示出幫助手冊,但是在linux/unix裏,我們有時候也鍵入全稱來輸入參數,比如說command --help 兩橫加上全稱,同樣也是表明我們需要幫助手冊.爲了能處理以上兩種情況,我們需要做更多的準備. 好了,下面的文字是網上拷來的,呵呵,這也是代碼複用的思想...
============================================
摘自冰火天地 《getopt_long及其使用》
========================================
Linux系統下,需要大量的命令行選項,如果自己手動解析他們的話實在是有違軟件複用的思想,不過還好,GNU C library留給我們一個解析命令行的接口(X/Open規範),好好使用它可以使你的程序改觀不少。
使用getopt_long()需要引入頭文件
#include <getopt.h>
現在我們使用一個例子來說明它的使用。
一個應用程序需要如下的短選項和長選項。
短選項 長選項 作用
-h --help 輸出程序命令行參數說明然後退出
-o filename --output filename 給定輸出文件名
-v --version 顯示程序當前版本後退後
爲了使用getopt_long函數,我們需要先確定兩個結構:
1.一個字符串,包括所需要的短選項字符,如果選項後有參數,字符後加一個":"符號。本例中,這個字符串應該爲"ho:v"。(因爲-o後面有參數filename,所以字符後面要加":")
2.一個包含長選項字符串的結構體數組,每一個結構體包含4個域,第一個域爲長選項字符串,第二個域是一個標識,只能爲0或1,分別代表沒有、有。第三個域永遠爲NULL。第四個域爲對應的短選項字符串。結構體數組的最後一個元素全部爲NULL和0,標識結束。在本例中,它應該像一下的樣子:
const struct option long_options[] = {
{ "help", 0, NULL, 'h' },
{ "output", 1, NULL, 'o' },
{ "version", 0, NULL, 'v' },
{ NULL, 0, NULL, 0}
};
調用時需要把main的兩個參數argc和argv以及上述兩個數據結構傳給getopt_long。
每次調用getopt_long,它會解析一個符號,返回相應的短選項字符,如果解析完畢返回-1。所以需要使用一個循環來處理所有的參數,而相應的循環裏會使用switch語句進行選擇。如果getopt_long遇到一個無效的選項字符,它會打印一個錯誤消息並且返回'?',很多程序會打印出幫助信息並且中止運行;當getopt_long解析到一個長選項並且發現後面沒有參數則返回':',表示缺乏參數。當處理一個參數時,全局變量optarg指向下一個要處理的變量。當getopt_long處理完所有的選項後,全局變量optind指向第一個未知的選項索引。
這一個例子代碼爲下:
- //編譯使用gcc -o getopt_long getopt_long.c
- #include <getopt.h>
- #include <stdio.h>
- #include <stdlib.h>
- /*程序的名字*/
- const char* program_name;
- /* 打印程序參數 */
- void print_usage (FILE* stream, int exit_code)
- {
- fprintf (stream, "Usage: %s options [ inputfile ... ]/n", program_name);
- fprintf (stream, " -h --help 顯示這個幫助信息./n"
- " -o --output filename 將輸出定位到文件./n"
- " -v --version 打印版本信息./n");
- exit (exit_code);
- }
- /* 主程序 */
- int main (int argc, char* argv[])
- {
- int next_option;//下一個要處理的參數符號
- int haveargv = 0;//是否有我們要的正確參數,一個標識
- /* 包含短選項字符的字符串,注意這裏的‘:’ */
- const char* const short_options = "ho:v";
- /* 標識長選項和對應的短選項的數組 */
- const struct option long_options[] = {
- { "help", 0, NULL, 'h' },
- { "output", 1, NULL, 'o' },
- { "version", 0, NULL, 'v' },
- { NULL, 0, NULL, 0 }};//最後一個元素標識爲NULL
- /* 此參數用於承放指定的參數,默認爲空 */
- const char* output_filename = NULL;
- /* 一個標誌,是否顯示版本號 */
- int verbose = 0;
- /* argv[0]始終指向可執行的文件文件名 */
- program_name = argv[0];
- do
- {
- next_option = getopt_long (argc, argv, short_options, long_options, NULL);
- switch (next_option)
- {
- case 'h': /* -h or --help */
- haveargv = 1;
- print_usage (stdout, 0);
- case 'o': /* -o or --output */
- /* 此時optarg指向--output後的filename */
- output_filename = optarg;
- haveargv = 1;
- break;
- case 'v': /* -v or --version */
- verbose = 1;
- haveargv = 1;
- break;
- case ':': /* 缺乏長選項內容 */
- break;
- case '?': /* 出現一個未指定的參數*/
- print_usage (stderr, 1);
- case -1: /* 處理完畢後返回-1 */
- if (!haveargv)
- {
- print_usage (stderr, 1);
- }
- break;
- default: /* 未指定的參數出現,出錯處理 */
- print_usage (stderr, 1);
- break;
- }
- }while (next_option !=-1);
- if (verbose)
- {
- int i;
- for (i = optind; i < argc; ++i)
- printf ("Argument: %s/n", argv[i]);
- }
- return 0;
- }
============================================
文章節錄完
============================================
把參數一個一個篩選出來後總要放一個地方吧,Clamav的作者選擇把參數選項嵌入到一個鏈表中以方便傳遞。那我們再來看看如何把得到參數加入鏈表。這裏以register_char_option爲例說明:
- /*短選項加入鏈表 比如--config_file=test 注意 這裏雖然是個長選項 但是它含有短字符代表,即是字符c {"config-file", 1, 0, 'c'} */
- void register_char_option(struct optstruct *opt, char ch)
- {
- struct optnode *newnode;
- newnode = (struct optnode *) mmalloc(sizeof(struct optnode));
- newnode->optchar = ch;
- if (optarg != NULL)
- {
- newnode->optarg = (char *) mcalloc(strlen(optarg) + 1, sizeof(char)); //參數值,這裏就是test
- strcpy(newnode->optarg, optarg);
- }
- else
- newnode->optarg = NULL;
- newnode->optname = NULL;
- newnode->next = opt->optlist; //連入鏈表
- opt->optlist = newnode;
- }
- struct optnode
- {
- char optchar; //短選項名 比如文中舉例的 h 選項
- char *optarg; //參數值, 有時候我們對參數要設定值的,比如 -p 23 這裏23就是參數值
- char *optname; //長選項名, 文中舉例的help這個字符串就應該放在這裏,跟前面h對應
- struct optnode *next; //下一個節點唄,我們要連成鏈表阿
- };
- struct optstruct
- {
- struct optnode *optlist;
- char *filename;
- };
這樣我們就得到了一個滿載參數的鏈表了,這樣函數傳參就會很簡單,因爲直接可以傳遞這個鏈表即可,這樣我們拿着鏈表就相當於得到了用戶所有需求,比如你看看main函數裏面馬上就跟着一句 clamd(opt); 這就是爲什麼我們要急着轉化爲鏈表的原因了。如果需要在鏈表中搜尋所需的參數那是很簡單的,只需要遍歷鏈表即可。比如:
- //尋找opt參數鏈表中是否含有指定字符ch的node
- int optc(const struct optstruct *opt, char ch)
- {
- struct optnode *handler;
- handler = opt->optlist;
- while (1)
- {
- if (handler)
- {
- if (handler->optchar == ch)
- return 1;
- }
- else
- break;
- handler = handler->next;
- }
- return(0);
- }
非常簡潔,因爲我們參數本來就不多,否則我們應該重新設計數據結構,比如我們要用hash表來快速定位。