Ir para conteúdo

POWERED BY:

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

_Isis_

[Resolvido] [Tutorial] [Drops C] Parsing dos Argumentos de um Pro

Recommended Posts

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

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.