Analisando argumentos da linha de comando?

Oi eu estou tentando escrever um programa que pode comparar dois arquivos linha por linha, palavra por palavra, ou caractere por caractere em C. Ele tem que ser capaz de ler em opções de linha de comando “-l -w -i ou – “… se a opção for -l, compara os arquivos linha por linha. se a opção for -w, compara os arquivos palavra por palavra. Se as opções forem – automaticamente assume que o próximo arg é o primeiro nome de arquivo. e se a opção for -i, ela os compara de maneira insensível a maiúsculas e minúsculas. Caso contrário, o padrão é comparar o caractere de arquivos por caractere

Não é preciso saber quantas vezes as opções são inseridas, desde que -w e -l não sejam introduzidas ao mesmo tempo e não haja mais ou menos que 2 arquivos.

Eu nem sei por onde começar a análise dos argumentos da linha de comando. POR FAVOR AJUDE 🙁

Então este é o código que eu criei para tudo. Eu ainda não verifiquei o erro ainda, mas eu queria saber se estou escrevendo as coisas de uma maneira 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; } 

Até onde sei, as três formas mais populares de analisar os argumentos da linha de comando em C são:

  • Getopt ( #include da Biblioteca C POSIX), que pode resolver tarefas simples de análise de argumentos . Se você está familiarizado com o bash, o getopt integrado do bash é baseado no Getopt da GNU libc.
  • Argp ( #include da GNU C Library), que pode resolver tarefas mais complexas e cuida de coisas como, por exemplo:
    • -? , --help para mensagem de ajuda , incluindo endereço de email
    • -V , --version para informações de versão
    • --usage para mensagem de uso
  • Fazendo você mesmo , o que eu não recomendo para programas que seriam dados a outra pessoa, já que há muita coisa que poderia dar errado ou uma qualidade inferior. O erro popular de esquecer ‘-‘ para parar a análise de opções é apenas um exemplo.

A documentação da biblioteca GNU C tem alguns bons exemplos para Getopt e Argp.

Exemplo para usar o 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. // ... } 

Exemplo para usar o 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); // ... } 

Exemplo para fazer você mesmo

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

Isenção de responsabilidade: Sou novo no Argp, o exemplo pode conter erros.

Use getopt() ou talvez 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); } } } 

Note que você precisa determinar quais headers include (eu faço 4 que são necessários), e a maneira que eu escrevi o tipo op_mode significa que você tem um problema no process() da function process() - você não pode acessar a enumeração lá embaixo. É melhor mover a enumeração para fora da function; você pode até mesmo tornar op_mode uma variável de escopo de arquivo sem binding externa (uma maneira sofisticada de dizer static ) para evitar passá-la para a function. Esse código não funciona - como sinônimo de input padrão, outro exercício para o leitor. Note que getopt() cuida automaticamente -- para marcar o final das opções para você.

Eu não executei nenhuma versão da digitação acima depois de um compilador; poderia haver erros nisso.


Para crédito extra, escreva uma function (biblioteca):

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

que encapsula a lógica para processar as opções de nome de arquivo após o loop getopt() . Deve manipular - como input padrão. Observe que usar isso indicaria que op_mode deve ser uma variável de escopo de arquivo estático. A function filter() usa argc , argv , optind e um ponteiro para a function de processamento. Ele deve retornar 0 (EXIT_SUCCESS) se ele puder abrir todos os arquivos e todas as invocações da function relatada 0, caso contrário, 1 (ou EXIT_FAILURE). Ter essa function simplifica a escrita de programas 'filter' no estilo Unix, que lêem arquivos especificados na linha de comando ou na input padrão.

Eu achei o Gengetopt bastante útil – você especifica as opções que você quer com um arquivo de configuração simples, e ele gera um par .c / .h que você simplesmente inclui e vincula ao seu aplicativo. O código gerado faz uso de getopt_long, parece manipular a maioria dos tipos comuns de parâmetros de linha de comando e pode economizar muito tempo.

Um arquivo de input do gengetopt pode ser parecido com isto:

 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 

Gerar o código é fácil e mostra cmdline.h e cmdline.c :

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

O código gerado é facilmente integrado:

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

Se você precisar fazer alguma verificação extra (como garantir que as flags sejam mutuamente exclusivas), você pode fazer isso com bastante facilidade com os dados armazenados na estrutura gengetopt_args_info .

Estou muito surpreso que ninguém mencionou o pacote “opt” de James Theiler.

Você pode encontrar opt em http://public.lanl.gov/jt/Software/

e um post lisonjeiro com alguns exemplos de como é muito mais simples do que outras abordagens está aqui:

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

Existe uma grande biblioteca C de propósito geral, a libUCW, que inclui análise de opções de linha de comando e carregamento de arquivos de configuração .

A biblioteca também vem com boa documentação e inclui algumas outras coisas úteis (rápido IO, estruturas de dados, alocadores, …), mas isso pode ser usado separadamente.

Exemplo de analisador da opção libUCW (a partir dos documentos da 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; } 

O Docopt tem uma implementação em C que achei bem legal: https://github.com/docopt/docopt.c

A partir de um formato padronizado de página do manual que descreve as opções da linha de comandos, docopt infere e cria um analisador de argumentos. Isso começou em python; a versão python literalmente apenas analisa a docstring e retorna um dict. Fazer isso em C leva um pouco mais de trabalho, mas é limpo de usar e não tem dependencies externas.

Eu escrevi uma pequena biblioteca que analisa argumentos semelhantes ao POpt, que eu tive vários problemas, chamado XOpt . Usa a análise de argumentos no estilo GNU e tem uma interface muito semelhante ao POpt.

Eu uso isso de tempos em tempos com grande sucesso, e funciona praticamente em qualquer 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; } 

Modelo instrucional para analisar argumentos de linha de comando em 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; } 

Tooting meu próprio chifre se eu puder, eu também gostaria de sugerir dar uma olhada em uma biblioteca de análise de opções que eu escrevi: dropt .

  • É uma biblioteca C (com um invólucro C ++, se desejar).
  • É leve.
  • É extensível (os tipos de argumentos personalizados podem ser facilmente adicionados e ter igualdade com tipos de argumentos integrados).
  • Deve ser muito portátil (está escrito no padrão C) sem dependencies (além da biblioteca padrão C).
  • Tem uma licença muito não restritiva (zlib / libpng).

Um recurso que muitos outros não oferecem é a capacidade de replace opções anteriores. Por exemplo, se você tiver um alias de shell:

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

e você quer usar bar mas com --flag1 desabilitado, ele permite que você faça:

 bar --flag1=0 

Ok, esse é o começo da longa história – feito curto ‘bort analisando uma linha de comando em 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"); } } 

Note que esta versão também suportará argumentos combinados: Então, ao invés de escrever / h / s -> / hs também funcionará.

Desculpe por ser a n-ésima pessoa postando aqui - no entanto, eu não estava realmente satisfeito com todas as versões autônomas que vi aqui. Bem, os libs são legais. Então, eu preferiria o analisador de opções libUCW , Arg ou Getopt, sobre os que são feitos em casa.

Note que você pode mudar:

*++argv[i] -> (++argv*)[0] mais longo, menos enigmático, mas ainda enigmático.

Ok, vamos dividi-lo: 1. argv [i] -> acessa o i-ésimo elemento no campo de ponteiro char-argv

  1. ++ * ... -> irá encaminhar o ponteiro argv por um caractere

  2. ... [0] -> seguirá o ponteiro, leia o caractere

  3. ++ (...) -> colchetes estão lá, então vamos aumentar o ponteiro e não o próprio valor do caractere.

Tão bom que Em C ## os pointers 'morreram' - viva os pointers !!!