¿Analizar argumentos de línea de comandos?

Hola, estoy intentando escribir un progtwig que pueda comparar dos archivos línea por línea, palabra por palabra o carácter por carácter en C. Tiene que poder leer en las opciones de línea de comando “-l -w -i o – “… si la opción es -l, compara los archivos línea por línea. si la opción es -w, compara los archivos palabra por palabra. si las opciones son, automáticamente asume que la siguiente arg es el primer nombre de archivo. y si la opción es -i, los compara de una manera que no distingue entre mayúsculas y minúsculas. De lo contrario, el valor predeterminado es comparar los archivos carácter por carácter

Se supone que no importa la cantidad de tiempo que se ingresan las opciones, siempre que -w y -l no se ingresen al mismo tiempo y no haya más o menos de 2 archivos.

Ni siquiera sé por dónde empezar con el análisis de los argumentos de la línea de comando. POR FAVOR AYUDA 🙁

Este es el código que se me ocurrió para todo. Todavía no lo he comprobado por error, pero me preguntaba si estoy escribiendo cosas de una manera demasiado complicada.

/* * 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; } 

Que yo sepa, las tres formas más populares de analizar argumentos de línea de comando en C son:

  • Getopt ( #include de la Biblioteca POSIX C), que puede resolver tareas simples de análisis de argumentos . Si estás un poco familiarizado con bash, el getopt incorporado de bash está basado en Getopt de GNU libc.
  • Argp ( #include de la Biblioteca GNU C), que puede resolver tareas más complejas y se encarga de cosas como, por ejemplo:
    • -? , --help por mensaje de ayuda , incluida la dirección de correo electrónico
    • -V , --version para información de la versión
    • --usage mensaje de uso para el uso
  • Haciéndolo tú mismo , que no recomiendo para progtwigs que se le darían a otra persona, ya que hay demasiado que podría salir mal o una calidad inferior. El error popular de olvidarse de ‘-‘ detener el análisis de opciones es solo un ejemplo.

La documentación de la Biblioteca GNU C tiene algunos buenos ejemplos para Getopt y Argp.

Ejemplo para usar Getopt

 #include  #include  #include  #include  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 ) is the index of the first non-option argument. // If it is >= argc, there were no non-option arguments. // ... } 

Ejemplo para usar Argp

 #include  #include  const char *argp_program_version = "programname programversion"; const char *argp_program_bug_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); // ... } 

Ejemplo para hacerlo usted mismo

 #include  #include  #include  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. // ... } 

Descargo de responsabilidad: soy nuevo en Argp, el ejemplo podría contener errores.

Usa getopt() , o quizás 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); } } } 

Tenga en cuenta que necesita determinar qué encabezados incluir (lo hago 4 que son necesarios), y la forma en que escribí el tipo op_mode significa que tiene un problema en el process() función process() - no puede acceder a la enumeración allí. Lo mejor es mover la enumeración fuera de la función; incluso podría hacer que op_mode una variable de scope de archivo sin enlace externo (una forma elegante de decir static ) para evitar pasarla a la función. Este código no funciona - como sinónimo de entrada estándar, otro ejercicio para el lector. Tenga en cuenta que getopt() se ocupa automáticamente de -- marcar el final de las opciones por usted.

No he ejecutado ninguna versión del tipeo anterior más allá de un comstackdor; podría haber errores en eso.


Para crédito adicional, escriba una función (biblioteca):

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

que encapsula la lógica para procesar las opciones de nombre de archivo después del bucle getopt() . Debe manejar - como entrada estándar. Tenga en cuenta que usar esto indicaría que op_mode debería ser una variable de scope de archivo estático. La función filter() toma argc , argv , optind y un puntero a la función de procesamiento. Debe devolver 0 (EXIT_SUCCESS) si fue capaz de abrir todos los archivos y todas las invocaciones de la función informó 0, de lo contrario 1 (o EXIT_FAILURE). Tener una función de este tipo simplifica la escritura de progtwigs de 'filtro' estilo Unix que leen los archivos especificados en la línea de comando o entrada estándar.

He descubierto que Gengetopt es bastante útil: usted especifica las opciones que desea con un archivo de configuración simple, y genera un par .c / .h que simplemente incluye y vincula con su aplicación. El código generado hace uso de getopt_long, parece manejar los tipos más comunes de parámetros de línea de comando y puede ahorrar mucho tiempo.

Un archivo de entrada gengetopt puede tener un aspecto similar a este:

 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 

Generar el código es fácil y cmdline.h y cmdline.c :

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

El código generado se integra fácilmente:

 #include  #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]); } } 

Si necesita realizar una comprobación adicional (como garantizar que los indicadores sean mutuamente exclusivos), puede hacerlo de forma bastante fácil con los datos almacenados en la estructura gengetopt_args_info .

Estoy muy sorprendido de que nadie mencionara el paquete “opt” de James Theiler.

Puede encontrar opt en http://public.lanl.gov/jt/Software/

y una publicación favorecedora con algunos ejemplos de cómo es mucho más simple que otros enfoques es aquí:

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

Hay una gran biblioteca de C de uso general, libUCW, que incluye un prolijo análisis de opciones de línea de comandos y carga de archivos de configuración .

La biblioteca también viene con buena documentación e incluye algunas otras cosas útiles (IO rápido, estructuras de datos, asignadores, …) pero esto se puede usar por separado.

Ejemplo de analizador de opciones libUCW (de los documentos de la biblioteca)

 #include  #include  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, "\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; } 

Docopt tiene una implementación en C que me pareció bastante agradable: https://github.com/docopt/docopt.c

A partir de un formato estandarizado de página de manual que describe las opciones de línea de comando, docopt infiere y crea un analizador de argumentos. Esto comenzó en Python; la versión de Python literalmente solo analiza el docstring y devuelve un dict. Hacer esto en C requiere un poco más de trabajo, pero está limpio de usar y no tiene dependencias externas.

Escribí una pequeña biblioteca que analiza argumentos similares a POpt, con los que tuve varios problemas, llamados XOpt . Utiliza el análisis de argumentos al estilo GNU y tiene una interfaz muy similar a POpt.

Lo uso de vez en cuando con gran éxito, y funciona prácticamente en cualquier lugar.

 #include  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; } 

Plantilla instruccional para analizar argumentos de línea de comando en C.

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  #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; } 

Tocar mi propia bocina si puedo, también me gustaría sugerir echar un vistazo a una biblioteca de análisis de opciones que he escrito: dropt .

  • Es una biblioteca C (con un contenedor C ++ si lo desea).
  • Es liviano
  • Es extensible (los tipos de argumentos personalizados se pueden agregar fácilmente y tienen el mismo nivel con los tipos de argumentos incorporados).
  • Debe ser muy portable (está escrito en C estándar) sin dependencias (que no sea la biblioteca estándar de C).
  • Tiene una licencia muy poco restrictiva (zlib / libpng).

Una característica que ofrece que muchos otros no tienen es la capacidad de anular las opciones anteriores. Por ejemplo, si tiene un alias de shell:

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

y desea utilizar la bar pero con --flag1 desactivado, le permite hacer:

 bar --flag1=0 

De acuerdo, ese es el comienzo de una larga historia: hecho un corto bort analizando una línea de comando en C …

 /** * Helper function to parse the command line * @param argc Argument Counter * @param argv Argument Vector * @param prog Program Instance Reference to fill with options */ bool parseCommandLine(int argc, char* argv[], DuplicateFileHardLinker* prog) { bool pathAdded = false; // iterate over all arguments... for ( int i = 1; iQuite mode"); logInfo(L" /v\t>Verbose mode"); logInfo(L" /d\t>Debug mode"); return false; // Log options case 'q': setLogLevel(LOG_ERROR); break; case 'v': setLogLevel(LOG_VERBOSE); break; case 'd': setLogLevel(LOG_DEBUG); break; default: logError(L"'%s' is an illegal command line option!" " Use /? to see valid options!", option); return false; } // switch one-char-option } //while one-char-options } //else one vs longer options } // if isArgAnOption // // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ So that's it! ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // What follows now is are some usefull extras... // else { // the command line options seems to be a path... WCHAR tmpPath[MAX_PATH_LENGTH]; mbstowcs(tmpPath, argv[i], sizeof(tmpPath)); // check if the path is existing! //... prog->addPath(tmpPath); //Comment or remove to get a working example pathAdded = true; } } // check for parameters if ( !pathAdded ) { logError("You need to specify at least one folder to process!\n" "Use /? to see valid options!"); return false; } return true; } int main(int argc, char* argv[]) { try { // parse the command line if ( !parseCommandLine(argc, argv, prog) ) { return 1; } // I know that sample is just to show how the nicely parse commandline Arguments // So Please excuse more nice useful C-glatter that follows now... } catch ( LPCWSTR err ) { DWORD dwError = GetLastError(); if ( wcslen(err) > 0 ) { if ( dwError != 0 ) { logError(dwError, err); } else { logError(err); } } return 2; } } #define LOG_ERROR 1 #define LOG_INFO 0 #define LOG_VERBOSE -1 #define LOG_DEBUG -2 /** Logging Level for the console output */ int logLevel = LOG_INFO; void logError(LPCWSTR message, ...) { va_list argp; fwprintf(stderr, L"ERROR: "); va_start(argp, message); vfwprintf(stderr, message, argp); va_end(argp); fwprintf(stderr, L"\n"); } void logInfo(LPCWSTR message, ...) { if ( logLevel < = LOG_INFO ) { va_list argp; va_start(argp, message); vwprintf(message, argp); va_end(argp); wprintf(L"\n"); } } 

Tenga en cuenta que esta versión también admitirá la combinación de argumentos: en lugar de escribir / h / s -> / hs también funcionará.

Perdón por ser la n-ésima persona que publica aquí; sin embargo, no estaba realmente satisfecho con todas las versiones independientes que vi aquí. Bueno, los lib son bonitos. Así que preferiría el analizador de opciones de libUCW , Arg o Getopt sobre los de fabricación casera.

Tenga en cuenta que puede cambiar:

*++argv[i] -> (++argv*)[0] más largo menos críptico pero aún críptico.

Bien, vamos a descomponerlo: 1. argv [i] -> acceder al elemento i-ésimo en el campo puntero argv-char

  1. ++ * ... -> reenviará el puntero argv con un char

  2. ... [0] -> seguirá el puntero leer el char

  3. ++ (...) -> el soporte está allí, así que boostemos el puntero y no el valor de char en sí.

Tan agradable que en C ## los punteros 'murieron' - ¡¡¡vivan los indicadores !!!