_Isis_ 202 Denunciar post Postado Novembro 10, 2008 Nese final de semestre o professor de Sistemas Operacionais passou um trabalho sobre threads. Incrivelmente as threads não foram o problema. O pepino foi a linha de comando: ./unifica [-v] [-np #] diretório. Bastaria fixar a ordem dos argumentos -- fiz isso pela simplicidade --, mas lembrei que várias vezes destruí meus arquivos porque o tar é "fresco" demais com as opções, então decidi procurar algo que tratasse as opções na linha de comando. O mais comum é ver programas que imprimem uma mensagem na tela e em seguida lêem um valor qualquer do teclado. #include <stdio.h> int main() { int x; printf("Digite um inteiro:"); scanf("%d",&x) printf("O quadrado de %d é %d\n", x, x*x); return 0; } Veja o caso do bzip2, por exemplo. Se a cada comando bzip2 o usuário tivesse que passar por todas as opções possíveis de serem usadas somente p/ comprimir um arquivo provavelmente ele iria desistir no meio do caminho. Uma saída é fixar a posição de cada opção e seu argumento (se ela possuir um), mas então a aplicação faclmente quebra se a ordem for trocada. Como exemplo, vamos criar um programa que conta o número de linhas de um arquivo. Isso é um requisito simples e nem é necessário definir opções na linha de comando, já que o único argumento além do nome do programa é o arquivo a ter as linhas contadas. Mas mesmo assim devemos cercar os possíveis erros que o usuário pode cometer, como se esquecer de digitar o nome do arquivo ou de fornecer um arquivo inexistente. #include <stdio.h> #include <stdlib.h> #define ERR_ARGS "Uso: %s <nome_do_arquivo>\n" #define ERR_FILE "Erro ao abrir o arquivo.\n" #define _GNU_SOURCE int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr,ERR_ARGS,argv[0]); return 1; } FILE *fp = fopen(argv[1],"r"); if (fp == NULL) { fprintf(stderr,ERR_FILE); return 1; } char *linha = NULL; unsigned int contador = 0; int retorno; size_t tmp; while (1) { retorno = getline(&linha,&tmp,fp); if (retorno == -1) break; if (retorno-1 > 0) contador++; if(feof(fp)) break; } fclose(fp); printf("%s : %u linhas\n",argv[1],contador); free(linha); return 0; } NOTA: a função getline é uma extensão GNU. Agora suponha que alguém pediu através de uma lista para que o programa imprimisse a quantidade de bytes do arquivo, sendo essa função indicada por -b, já que o programa se desviou da função original. As seguintes linhas de comando são válidas: ./conta_linhas arquivo ./conta_linhas -b arquivo Então temos que argc varia entre 2 e 3. Isso nos obriga a modificar o primeiro if do programa: if (argc < 2 || argc > 3) { fprintf(stderr,ERR_ARGS,argv[0]); return 1; } Começam as complicações, pois como o programa original tinha uma única função, a posição do arquivo em argv era fixa. Agora, se deixarmos em argv[argc-1], ou o arquivo não existe ou o programa tentou abrir a opção -b como um arquivo. #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #define ERR_ARGS "Uso: %s [-b] <nome_do_arquivo>\n" #define ERR_FILE "Erro ao abrir o arquivo.\n" #define _GNU_SOURCE void contaLinhas(FILE *fp) { char *linha = NULL; unsigned int contador = 0; int retorno; size_t tmp; while (1) { retorno = getline(&linha,&tmp,fp); if (retorno == -1) break; if (retorno-1 > 0) contador++; if(feof(fp)) break; } free(linha); printf(" %u linha(s) ",contador); } void contaBytes(const char *arquivo) { struct stat buf; stat(arquivo, &buf); printf(" %u byte(s) ",buf.st_size); } int main(int argc, char *argv[]) { if (argc < 2 || argc > 3) { fprintf(stderr,ERR_ARGS,argv[0]); return 1; } printf("%s : ", argv[argc-1]); FILE *fp = fopen(argv[argc-1],"r"); if (fp == NULL) { fprintf(stderr,ERR_FILE); return 1; } contaLinhas(fp); fclose(fp); if (argc == 3) contaBytes(argv[argc-1]); puts(""); return 0; } O programa continua fazendo coisas simples, mas quando se adiciona uma opção à linha de comando aumenta-se a complexidade do main. Pense nisso como uma mudança de requisitos vinda de um cliente que não sabe o que realmente quer. Até que ele se decida, seu programa pode parecer um ornitorrinco devido à quantidade de coisas "penduradas". Mas até o desenvolvedor olha p/ o programa e vê que pode acrescentar mais coisas. Vamos inserir a contagem de palavras, especificada pela opção -w. Para complicar, podemos contar todas as palavras do arquivo ou simplesmente uma palavra passada como argumento da opção. Vamos criar mais uma opção para esse último caso: -W. Nesse ponto temos as seguintes linhas de comando válidas, sem contar a permutação entre as opções: 1) ./conta_linhas arquivo 2) ./conta_linhas -b arquivo 3) ./conta_linhas -w arquivo 4) ./conta_linhas -W palavra arquivo 5) ./conta_linhas -b -w arquivo 6) ./conta_linhas -b -W palavra arquivo 7) ./conta_linhas -b -W palavra -w arquivo À primeira vista você entra em pânico, porque vai ter que modificar as condições que envolvem o argc e testar os elementos de argv quando houver linha de comando com igual número de argumentos, como ocorre entre as linhas 2 e 3 e entre 4 e 5. Além disso, é costume incluir a opção -h, que exibe um resumo das opções do programa. O if já não é tão atraente pra esse tipo de coisa, mesmo que você tente percorrer o argv em busca de espaços e ir salvando as palavras que você encontra. É muito trabalho p/ pouca coisa. P/ isso serve a função getopt. Para usá-la precisamos incluir o header unistd. Como Funciona O protótipo da função é int getopt(int argc, char * const argv[],const char *optstring). Os dois primeiros argumentos são o argc e o argv de main(). A novidade aqui é a optstring : é através dela que você especifica as opções e a presença ou não de argumentos dessas opções. Um argumento de uma opção é obrigatório se a letra da opção for seguida de dois-pontos. Então, tomando a opção -W como exemplo, temos que ela seria representada como "W:". Transformando todas as opções, temos a string "bwW:", sendo que as opções -b e -w não possuem argumentos. Existem as seguintes variáveis: optarg : recebe o argumento da opção (se ela aceitar algum) ou NULL caso contrário. Além disso, quando a última opção for analisada, optarg=NULL. optind : possui o índice para a próxima opção da linha de comando. optopt : armazena a opção que não foi reconhecida pelo getopt. Além disso, quando a getopt não reconhece uma opção, ela retorna o caractere '?'. opterr : se for definida para 0, a getopt não imprime a mensagem de erro quando encontrar uma opção desconhecida. Para tornar as coisas mais bonitas, o tratamento de erros está embutido. Execute o seguinte programa que não faz absolutamente nada de útil, mas não forneça nenhum argumento à opção -t: #include <stdio.h> #include <unistd.h> int main(int argc, char *argv[]) { const char *opcoes = "nt:"; while (getopt(argc, argv, &opcoes) != -1); return 0; } Mas repare que se você adicionar algumas linhas de impressão e resolver executar o programa com algo entre o nome do programa e a opção, essa coisa não vai aparecer na tela (i.e. não vai ser reconhecida pelo getopt), a não ser que comece com '-' ou que seja um argumento de alguma opção. E nesse caso, se não estiver entre as opções especificadas na string, o programa retornará uma mensagem de erro, dizendo que é uma opção inválida -- outra verificação que não precisamos fazer. #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #define ERR_ARGS "Uso: %s [-b] [-w] [-W <palavra>] <nome_do_arquivo>\n" #define ERR_FILE "Erro ao abrir o arquivo.\n" #define _GNU_SOURCE void contaLinhas(FILE *fp) { char *linha = NULL; unsigned int contador = 0; int retorno; size_t tmp; while (1) { retorno = getline(&linha,&tmp,fp); if (retorno == -1) break; if (retorno-1 > 0) contador++; if(feof(fp)) break; } free(linha); printf("%u linha(s)\n",contador); } void contaBytes(const char *arquivo) { struct stat buf; stat(arquivo, &buf); printf("%u byte(s)\n",buf.st_size); } void contaPalavras(const char *fp, const char * palavra) { char * comando; int i,j; FILE *P; char resultado[50]; if (palavra == NULL) { const char * wccomm = "wc -w "; comando = (char*)malloc(strlen(wccomm)+strlen(fp)+1); strcpy(comando,wccomm); for(i=strlen(wccomm),j=0;j<strlen(fp)+1;j++,i++) comando[i] = fp[j]; P = popen(comando,"r"); fgets(resultado,50,P); pclose(P); (*strchr(resultado,(int)' ')) = '\0'; printf("%s palavras\n",resultado); free(comando); } else { const char * wccomp = "grep "; const char *opcao = " -c "; comando = (char*)malloc(strlen(wccomp)+strlen(opcao)+strlen(fp)+strlen(palavra)+1); strcpy(comando,wccomp); for(i=strlen(wccomp),j=0;j<strlen(palavra)+1;i++,j++) comando[i] = palavra[j]; int tamanho = strlen(wccomp)+strlen(palavra); for(i=tamanho,j=0;j<strlen(opcao);i++,j++) comando[i] = opcao[j]; tamanho = tamanho+strlen(opcao); for(i=tamanho,j=0;j<strlen(fp)+1;i++,j++) comando[i] = fp[j]; P = popen(comando,"r"); fgets(resultado,50,P); pclose(P); (*strchr(resultado,(int)'\n')) = '\0'; printf("%s ocorrencias de %s\n",resultado,palavra); free(comando); } } int main(int argc, char *argv[]) { if (argc < 2 || argc > 6) { fprintf(stderr,ERR_ARGS,argv[0]); return 1; } const char *opcoes = "bwW:"; int letra; FILE *fp = fopen(argv[argc-1],"r"); if (fp == NULL) { fprintf(stderr,ERR_FILE); return 1; } contaLinhas(fp); while ( (letra = getopt(argc, argv,opcoes)) != -1) { if ( (char)letra == 'b') contaBytes(argv[argc-1]); if ( (char)letra == 'w') contaPalavras(argv[argc-1],NULL); if ( (char)letra == 'W') contaPalavras(argv[argc-1],optarg); } fclose(fp); return 0; } Agora podemos usar as opções em qualquer ordem, mantendo o arquivo como argumento fixo na última posição e sem calcular todas as permutações possíveis nem fixar a ordem de todos os argumentos. PS: se vcs não têm Linux p/ testar esses códigos -- por causa da getline --, arrumem um Ubuntu e instalem uma máquina virtual (www.easyvmx.com). Compartilhar este post Link para o post Compartilhar em outros sites