parsing命令行参数?

嗨,我想写一个程序,可以比较两个文件逐行,逐字,或逐字符的字符C.它必须能够读取命令行选项“-l -w -i或 – “…如果选项是-l它逐行比较文件。 如果选项是-w,则逐字比较文件。 如果选项是 – 它会自动假定下一个参数是第一个文件名。 如果选项是-i,则以不区分大小写的方式进行比较。 否则,默认情况下会逐个比较文件

只要-w和-l没有同时input,并且没有多于或less于2个文件,就不需要input多less次这些选项。

我甚至不知道从哪里开始parsing命令行参数。 请帮忙 :(

所以这就是我为所有事情想出的代码。 我还没有错误检查它,但我想知道如果我写的东西过于复杂的方式吗?

/* * Functions to compare files. */ int compare_line(); int compare_word(); int compare_char(); int case_insens(); /* * Program to compare the information in two files and print message saying * whether or not this was successful. */ int main(int argc, char* argv[]) { /*Loop counter*/ size_t i = 0; /*Variables for functions*/ int caseIns = 0; int line = 0; int word = 0; /*File pointers*/ FILE *fp1, *fp2; /* * Read through command-line arguments for options. */ for (i = 1; i < argc; i++) { printf("argv[%u] = %s\n", i, argv[i]); if (argv[i][0] == '-') { if (argv[i][1] == 'i') { caseIns = 1; } if (argv[i][1] == 'l') { line = 1; } if (argv[i][1] == 'w') { word = 1; } if (argv[i][1] == '-') { fp1 = argv[i][2]; fp2 = argv[i][3]; } else { printf("Invalid option."); return 2; } } else { fp1(argv[i]); fp2(argv[i][1]); } } /* * Check that files can be opened. */ if(((fp1 = fopen(fp1, "rb")) == NULL) || ((fp2 = fopen(fp2, "rb")) == NULL)) { perror("fopen()"); return 3; } else{ if (caseIns == 1) { if(line == 1 && word == 1) { printf("That is invalid."); return 2; } if(line == 1 && word == 0) { if(compare_line(case_insens(fp1, fp2)) == 0) return 0; } if(line == 0 && word == 1) { if(compare_word(case_insens(fp1, fp2)) == 0) return 0; } else { if(compare_char(case_insens(fp1,fp2)) == 0) return 0; } } else { if(line == 1 && word == 1) { printf("That is invalid."); return 2; } if(line == 1 && word == 0) { if(compare_line(fp1, fp2) == 0) return 0; } if(line == 0 && word == 1) { if(compare_word(fp1, fp2) == 0) return 0; } else { if(compare_char(fp1, fp2) == 0) return 0; } } } return 1; if(((fp1 = fclose(fp1)) == NULL) || (((fp2 = fclose(fp2)) == NULL))) { perror("fclose()"); return 3; } else { fp1 = fclose(fp1); fp2 = fclose(fp2); } } /* * Function to compare two files line-by-line. */ int compare_line(FILE *fp1, FILE *fp2) { /*Buffer variables to store the lines in the file*/ char buff1 [LINESIZE]; char buff2 [LINESIZE]; /*Check that neither is the end of file*/ while((!feof(fp1)) && (!feof(fp2))) { /*Go through files line by line*/ fgets(buff1, LINESIZE, fp1); fgets(buff2, LINESIZE, fp2); } /*Compare files line by line*/ if(strcmp(buff1, buff2) == 0) { printf("Files are equal.\n"); return 0; } printf("Files are not equal.\n"); return 1; } /* * Function to compare two files word-by-word. */ int compare_word(FILE *fp1, FILE *fp2) { /*File pointers*/ FILE *fp1, *fp2; /*Arrays to store words*/ char fp1words[LINESIZE]; char fp2words[LINESIZE]; if(strtok(fp1, " ") == NULL || strtok(fp2, " ") == NULL) { printf("File is empty. Cannot compare.\n"); return 0; } else { fp1words = strtok(fp1, " "); fp2words = strtok(fp2, " "); if(fp1words == fp2words) { fputs(fp1words); fputs(fp2words); printf("Files are equal.\n"); return 0; } } return 1; } /* * Function to compare two files character by character. */ int compare_char(FILE *fp1,FILE *fp2) { /*Variables to store the characters from both files*/ int c; int d; /*Buffer variables to store chars*/ char buff1 [LINESIZE]; char buff2 [LINESIZE]; while(((c = fgetc(fp1))!= EOF) && (((d = fgetc(fp2))!=EOF))) { if(c == d) { if((fscanf(fp1, "%c", buff1)) == (fscanf(fp2, "%c", buff2))) { printf("Files have equivalent characters.\n"); return 1; break; } } } return 0; } /* * Function to compare two files in a case-insensitive manner. */ int case_insens(FILE *fp1, FILE *fp2, size_t n) { /*Pointers for files.*/ FILE *fp1, *fp2; /*Variable to go through files.*/ size_t i = 0; /*Arrays to store file information.*/ char fp1store[LINESIZE]; char fp2store[LINESIZE]; while(!feof(fp1) && !feof(fp2)) { for(i = 0; i < n; i++) { fscanf(fp1, "%s", fp1store); fscanf(fp2, "%s", fp2store); fp1store = tolower(fp1store); fp2store = tolower(fp2store); return 1; } } return 0; } 

据我所知,如何在C中parsing命令行参数的三种最stream行的方法是:

  • Getopt (POSIX C库中的#include <unistd.h> ),可以解决简单的参数parsing任务。 如果你对bash有点熟悉,bash的getopt内置是基于GNU libc的Getopt。
  • Argp (来自GNU C库的#include <argp.h> ),它可以解决更复杂的任务并处理诸如以下内容之类的内容:
    • -?帮助帮助信息 ,包括电子邮件地址
    • -V--version 版本信息版本
    • --usage 使用消息使用
  • 自己去做,我不build议这样做 ,因为有太多可能会出错或质量较差的项目。 忘记“ – ”停止选项parsing的stream行错误就是一个例子。

GNU C库文档中有Getopt和Argp的一些很好的例子。

使用Getopt的示例

 #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char *argv[]) { bool isCaseInsensitive = false; int opt; enum { CHARACTER_MODE, WORD_MODE, LINE_MODE } mode = CHARACTER_MODE; while ((opt = getopt(argc, argv, "ilw")) != -1) { switch (opt) { case 'i': isCaseInsensitive = true; break; case 'l': mode = LINE_MODE; break; case 'w': mode = WORD_MODE; break; default: fprintf(stderr, "Usage: %s [-ilw] [file...]\n", argv[0]); exit(EXIT_FAILURE); } } // Now optind (declared extern int by <unistd.h>) is the index of the first non-option argument. // If it is >= argc, there were no non-option arguments. // ... } 

使用Argp的示例

 #include <argp.h> #include <stdbool.h> const char *argp_program_version = "programname programversion"; const char *argp_program_bug_address = "<your@email.address>"; static char doc[] = "Your program description."; static char args_doc[] = "[FILENAME]..."; static struct argp_option options[] = { { "line", 'l', 0, 0, "Compare lines instead of characters."}, { "word", 'w', 0, 0, "Compare words instead of characters."}, { "nocase", 'i', 0, 0, "Compare case insensitive instead of case sensitive."}, { 0 } }; struct arguments { enum { CHARACTER_MODE, WORD_MODE, LINE_MODE } mode; bool isCaseInsensitive; }; static error_t parse_opt(int key, char *arg, struct argp_state *state) { struct arguments *arguments = state->input; switch (key) { case 'l': arguments->mode = LINE_MODE; break; case 'w': arguments->mode = WORD_MODE; break; case 'i': arguments->isCaseInsensitive = true; break; case ARGP_KEY_ARG: return 0; default: return ARGP_ERR_UNKNOWN; } return 0; } static struct argp argp = { options, parse_opt, args_doc, doc, 0, 0, 0 }; int main(int argc, char *argv[]) { struct arguments arguments; arguments.mode = CHARACTER_MODE; arguments.isCaseInsensitive = false; argp_parse(&argp, argc, argv, 0, 0, &arguments); // ... } 

自己做的例子

 #include <stdbool.h> #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { bool isCaseInsensitive = false; enum { CHARACTER_MODE, WORD_MODE, LINE_MODE } mode = CHARACTER_MODE; size_t optind; for (optind = 1; optind < argc && argv[optind][0] == '-'; optind++) { switch (argv[optind][1]) { case 'i': isCaseInsensitive = true; break; case 'l': mode = LINE_MODE; break; case 'w': mode = WORD_MODE; break; default: fprintf(stderr, "Usage: %s [-ilw] [file...]\n", argv[0]); exit(EXIT_FAILURE); } } // *argv points to the remaining non-option arguments. // If *argv is NULL, there were no non-option arguments. // ... } 

免责声明:我是Argp的新手,这个例子可能包含错误。

使用getopt()或者getopt_long()

 int iflag = 0; enum { WORD_MODE, LINE_MODE } op_mode = WORD_MODE; // Default set int opt; while ((opt = getopt(argc, argv, "ilw") != -1) { switch (opt) { case 'i': iflag = 1; break; case 'l': op_mode = LINE_MODE; break; case 'w': op_mode = WORD_MODE; break; default: fprintf(stderr, "Usage: %s [-ilw] [file ...]\n", argv[0]); exit(EXIT_FAILURE); } } /* Process file names or stdin */ if (optind >= argc) process(stdin, "(standard input)", op_mode); else { int i; for (i = optind; i < argc; i++) { FILE *fp = fopen(argv[i], "r"); if (fp == 0) fprintf(stderr, "%s: failed to open %s (%d %s)\n", argv[0], argv[i], errno, strerror(errno)); else { process(fp, argv[i], op_mode); fclose(fp); } } } 

请注意,您需要确定要包含哪些头(我要求它是4),而我写op_modetypes的方式意味着在函数process()有一个问题 – 您无法在其中访问枚举。 最好将枚举移动到函数之外; 你甚至可以使op_mode成为一个没有外部连接的文件范围variables(一种说static的奇特方式),以避免将其传递给函数。 这个代码并不处理-作为标准input的同义词,是读者的另一个练习。 请注意, getopt()自动处理--标记选项的结尾。

我还没有运行任何版本的上面通过编译器的input; 可能会有错误。


为了额外的功劳,写一个(库)function:

 int filter(int argc, char **argv, int idx, int (*function)(FILE *fp, const char *fn)); 

它在getopt()循环之后封装了处理文件名选项的逻辑。 它应该处理-作为标准input。 请注意,使用这将表明op_mode应该是一个静态文件范围variables。 filter()函数使用argcargvoptind和指向处理函数的指针。 它应该返回0(EXIT_SUCCESS),如果它能够打开所有的文件,并且函数的所有调用都报告0,否则返回1(或EXIT_FAILURE)。 具有这样的function简化了编写读取命令行或标准input中指定的文件的Unix风格的“filter”程序。

我发现Gengetopt是非常有用的 – 你可以用一个简单的configuration文件来指定你想要的选项,并且它会生成一个.c / .h对,你只需简单地包含并链接到你的应用程序。 生成的代码使用getopt_long,似乎处理大多数常见的命令行参数,并且可以节省大量的时间。

gengetoptinput文件可能如下所示:

 version "0.1" package "myApp" purpose "Does something useful." # Options option "filename" f "Input filename" string required option "verbose" v "Increase program verbosity" flag off option "id" i "Data ID" int required option "value" r "Data value" multiple(1-) int optional 

生成代码很容易,吐出cmdline.hcmdline.c

 $ gengetopt --input=myApp.cmdline --include-getopt 

生成的代码很容易整合:

 #include <stdio.h> #include "cmdline.h" int main(int argc, char ** argv) { struct gengetopt_args_info ai; if (cmdline_parser(argc, argv, &ai) != 0) { exit(1); } printf("ai.filename_arg: %s\n", ai.filename_arg); printf("ai.verbose_flag: %d\n", ai.verbose_flag); printf("ai.id_arg: %d\n", ai.id_arg); int i; for (i = 0; i < ai.value_given; ++i) { printf("ai.value_arg[%d]: %d\n", i, ai.value_arg[i]); } } 

如果您需要进行额外的检查(例如确保标记互斥),则可以使用gengetopt_args_info结构中存储的数据轻松完成此操作。

我很惊讶没有人提起James Theiler的“select”包。

你可以在http://public.lanl.gov/jt/Software/findopt。;

以及一些令人捧腹的post,其中有一些例子说明如何比其他方法简单得多:

http://www.decompile.com/not_invented_here/opt/

有一个很好的通用C库libucw ,其中包括整洁的命令行选项parsing和configuration文件加载 。

该库还提供了很好的文档,包括一些其他有用的东西(快速IO,数据结构,分配器,…),但这可以单独使用。

示例libucW选项parsing器(来自库文档)

 #include <ucw/lib.h> #include <ucw/opt.h> int english; int sugar; int verbose; char *tea_name; static struct opt_section options = { OPT_ITEMS { OPT_HELP("A simple tea boiling console."), OPT_HELP("Usage: teapot [options] name-of-the-tea"), OPT_HELP(""), OPT_HELP("Options:"), OPT_HELP_OPTION, OPT_BOOL('e', "english-style", english, 0, "\tEnglish style (with milk)"), OPT_INT('s', "sugar", sugar, OPT_REQUIRED_VALUE, "<spoons>\tAmount of sugar (in teaspoons)"), OPT_INC('v', "verbose", verbose, 0, "\tVerbose (the more -v, the more verbose)"), OPT_STRING(OPT_POSITIONAL(1), NULL, tea_name, OPT_REQUIRED, ""), OPT_END } }; int main(int argc, char **argv) { opt_parse(&options, argv+1); return 0; } 

我写了一个小的库来parsing类似于POpt的参数,我有几个问题,称为XOpt 。 使用GNU风格的参数parsing,并有一个非常类似于POpt的接口。

我不时地使用它,取得了巨大的成功,而且几乎可以在任何地方使用。

Docopt有一个C实现,我认为是相当不错的: https : //github.com/docopt/docopt.c

从描述命令行选项的手册页标准化格式中,docopt推断并创build一个参数parsing器。 这开始在Python中; Python版本从字面上parsing文档string,并返回一个字典。 在C中做这个需要多一点的工作,但它使用干净,没有外部依赖。

 #include <stdio.h> int main(int argc, char **argv) { size_t i; size_t filename_i = -1; for (i = 0; i < argc; i++) { char const *option = argv[i]; if (option[0] == '-') { printf("I am a flagged option"); switch (option[1]) { case 'a': /*someting*/ break; case 'b': break; case '-': /* "--" -- the next argument will be a file.*/ filename_i = i; i = i + 1; break; default: printf("flag not recognised %s", option); break; } } else { printf("I am a positional argument"); } /* At this point, if -- was specified, then filename_i contains the index into argv that contains the filename. If -- was not specified, then filename_i will be -1*/ } return 0; } 

用于parsingC中命令行参数的指令模板

C:> programName -w – fileOne.txt fileTwo.txt

 BOOL argLine = FALSE; BOOL argWord = FALSE; BOOL argChar = FALSE; char * fileName1 = NULL; char * fileName2 = NULL; int main(int argc, char * argv[]) { int i; printf("Argument count=%d\n",argc); for (i = 0; i < argc; i++) { printf("Argument %s\n",argv[i]); if (strcmp(argv[i],"-l")==0) { argLine = TRUE; printf(" argLine=TRUE\n"); } else if (strcmp(argv[i],"-w")==0) { argWord = TRUE; printf(" argWord=TRUE\n"); } else if (strcmp(argv[i],"-c")==0) { argChar = TRUE; printf(" argChar=TRUE\n"); } else if (strcmp(argv[i],"--")==0) { if (i+1 <= argc) { fileName1 = argv[++i]; printf(" fileName1=%s\n",fileName1); } if (i+1 <= argc) { fileName2 = argv[++i]; printf(" fileName2=%s\n",fileName2); } } } return 0; } 
  /* Here's a rough one not relying on any libraries. Example: -wi | -iw //word case insensitive -li | -il //line case insensitive -- file //specify the first filename (you could just get the files as positional arguments in the else statement instead) PS: don't mind the #define's, they're just pasting code :D */ #ifndef OPT_H #define OPT_H //specify option requires argument #define require \ optarg = opt_pointer + 1; \ if (*optarg == '\0') \ { \ if (++optind == argc) \ goto opt_err_arg; \ else \ optarg = argv[optind]; \ } \ opt_pointer = opt_null_terminator; //start processing argv #define opt \ int optind = 1; \ char *opt_pointer = argv[1]; \ char *optarg = NULL; \ char opt_null_terminator[2] = {'\0','\0'}; \ if (0) \ { \ opt_err_arg: \ fprintf(stderr,"option %c requires argument.\n",*opt_pointer); \ return 1; \ opt_err_opt: \ fprintf(stderr,"option %c is invalid.\n",*opt_pointer); \ return 1; \ } \ for (; optind < argc; opt_pointer = argv[++optind]) \ if (*opt_pointer++ == '-') \ { \ for (;;++opt_pointer) \ switch (*opt_pointer) \ { //stop processing argv #define done \ default: \ if (*opt_pointer != '\0') \ goto opt_err_opt; \ else \ goto opt_next; \ break; \ } \ opt_next:; \ } #endif //opt.h #include <stdio.h> #include "opt.h" int main (int argc, char **argv) { #define by_character 0 #define by_word 1 #define by_line 2 int cmp = by_character; int case_insensitive = 0; opt case 'h': puts ("HELP!"); break; case 'v': puts ("fileCMP Version 1.0"); break; case 'i': case_insensitive = 1; break; case 'w': cmp = by_word; break; case 'l': cmp = by_line; break; case '-':required printf("first filename: %s\n", optarg); break; done else printf ("Positional Argument %s\n", argv[optind]); return 0; } 

如果可以的话,请自己动手,我也想build议看看我写的一个选项parsing库: dropt 。

  • 这是一个C库(如果需要,可以使用C ++包装器)。
  • 这是轻量级的。
  • 它是可扩展的(自定义参数types可以很容易地添加,并与内置的参数types具有相同的基础)。
  • 它应该是非常便携的(它是用C编写的),没有依赖关系(除了C标准库)。
  • 它有一个非常不受限制的许可(zlib / libpng)。

它提供了许多其他function的一个特点是能够覆盖早期的选项。 例如,如果你有一个shell别名:

 alias bar="foo --flag1 --flag2 --flag3" 

而你想用bar但是用--flag1禁用,它可以让你做到:

 bar --flag1=0