Publi

Cómo gestionar los parámetros de nuestros programas con getopt en C

fruit-stall-paris-1500x1000

Cuando hablamos de argumentos o parámetros de un programa en C, debemos pensa en los parámetros tan raros que pasamos a la función main(). En este caso, podemos ver cómo con dos argumentos (argc, o el número de argumentos que tenemos y argv o el contenido de esos argumentos, somos capaces de gestionar la información que un usuario nos pasa justo cuando ejecuta nuestro programa.

Lo más sencillo que podemos hacer con los argumentos

Imaginemos este ejemplo, de un programa donde queremos copiar un archivo origen a un destino (como cp), primero comprobamos que el número de argumentos es suficiente y luego imprimimos en pantalla el valor de cada uno (es un ejemplo inofensivo, no vamos a copiar nada). Tenemos que tener en cuenta que argv[0] encerrará el propio nombre del ejecutable del programa.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
  if (argc<3)
    {
      printf ("Debe especificar origen y destino\n");
      exit(1);
    }

  printf ("Origen: %s\n", argv[1]);
  printf ("Destino: %s\n", argv[2]);
  /* Copiamos el archivo */

  return 0;
}

A este programa debemos llamarle de la siguiente manera:
Screenshot 05-11-2015-031107

Si tengo esto, ¿para qué quiero getopt?

Las opciones que nos da utilizar a pelo son algo limitadas, es decir, si tienes experiencia en la línea de comandos, verás que muchos programas tienen argumentos más flexibles, es decir, tenemos argumentos que activarán algo en el programa, por ejemplo:

$ ls -l

que listará archivos en modo lista, o

$ du -h

que expresará los tamaños en formato humano (Kb, Mb, Gb,…)

Otros argumentos irán acompañados de un valor, como por ejemplo:

$ gcc -o args args.c

para compilar un archivo, tenemos que especificar el archivo salida (output) con -o delante, o:

$ wmctrl -s 3

para saltar al tercer escritorio desde nuestra línea de comandos (requiere wmctrl instalado).

También nos podríamos encontrar el caso de un argumento que reciba un argumento de forma voluntaria como el argumento -p del cliente mysql, por lo que estas dos opciones son válidas:

$ mysql -p

y

$ mysql -p clave

Si usamos la primera forma, nos preguntará la contraseña por teclado y será invisible en pantalla, si usamos la segunda, debemos pasar la contraseña y ésta será visible.

Ni que decir tiene que un buen gestor de parámetros nos debe permitir introducir múltiples argumentos, de diferente índole, y en muchos casos, sin importar el orden de éstos, y por ejemplo usarlos todos juntos, por ejemplo como hace cp:

$ cp -rvpfi origen destino

que nos evita tener que escribir:

$ cp -r -v -p -f -i origen destino

Un pequeño ejemplo de getopt()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/**
*************************************************************
* @file connect.c
* @brief Input argument management example with getopt
*
* @author Gaspar Fernández <blakeyed@totaki.com>
* @version
*
*************************************************************/


#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>
#include <ctype.h>
#include <string.h>
#include <termios.h>

void help(char *programa)
{
  fprintf (stderr, "Ejemplo getopt Ver 0.2\nCopyright (c) 2015 xxxx\n\n");
  fprintf (stderr, "Este programa hace como que va a conectar con un servidor externo\n"
       "y tienes que pasarle algunos argumentos para especificar la conexión.\n");
  fprintf (stderr, "Uso %s -h host [OPCIONES] [ficheros]\n", programa);
  fprintf (stderr, "  -?, -H\t\tPresenta esta ayuda en pantalla.\n");
  fprintf (stderr, "  -v\t\tActiva el modo verbose.\n");
  fprintf (stderr, "  -u usuario\t\tEspecifica el nombre de usuario.\n");
  fprintf (stderr, "  -p [password]\t\tIntroduce la contraseña.\n");
  fprintf (stderr, "  -P puerto\t\tEspecifica el puerto de conexión\n\n");
  exit (2);
}

char* pide_password()
{
  struct termios t, ct;
  char* temp = (char*)malloc(100*sizeof(char));

  printf ("Introduce tu clave: ");
  tcgetattr(0, &t);
  ct=t;
  t.c_lflag |= ~ECHO;
  tcsetattr(0, TCSANOW, &t);
  scanf("%s", temp);
  tcsetattr(0, TCSANOW, &ct);

  return temp;
}

int main(int argc, char *argv[])
{
  int verbose = 0;      /* flag */
  char *user=NULL, *pass=NULL, *host=NULL;
  int port = 0;
  int i;
  int l;
  int c;

  /* variables globales de getopt */
  extern char* optarg;
  extern int optind;
  extern int optopt;
  extern int opterr;
  opterr = 0;

  while ((c = getopt (argc, argv, "vu:p::P:h:")) != -1)
    switch (c)
      {
      case 'v':
    verbose=1;
        break;
      case 'u':
    user = optarg;
        break;
      case 'p':
    if (!optarg)
      pass = pide_password();
    else
      {
        pass = malloc(strlen(optarg));
        strcpy(pass, optarg);
        /* Llenamos el password de asteriscos para que no sea visible
           con ps, por ejemplo. */

        for (l=0; l<strlen(optarg); ++l)
          optarg[l]='*';
      }
        break;
      case 'P':
    port = atoi(optarg);
    break;
      case 'h':
    host = optarg;
    break;
      case 'H':
    help(argv[0]);
    break;
      case '?':
        if (strchr("uPh", optopt) != NULL)
          fprintf (stderr, "La opción -%c requiere un argumento.\n", optopt);
    else if (optopt=='?')
      help(argv[0]);
        else if (isprint (optopt))
          fprintf (stderr, "Opción desconocida'-%c'.\n", optopt);
        else
          fprintf (stderr, "Carácter no válido '\\x%x'.\n", optopt);

    help(argv[0]);
      default:
    exit(1);
      }

  /* Comprobamos los argumentos obligatorios */
  if (!host)
    {
      fprintf (stderr, "Debe especificar el host de forma obligatoria!!\n");
      help(argv[0]);
    }

  if (host)
    printf ("Host: %s\n", host);
  if (port)
    printf ("Port: %d\n", port);
  if (user)
    printf ("User: %s\n", user);
  if (pass)
    printf ("Pass: %s\n", pass);

  printf ("Verb: %d\n", verbose);

  for (i = optind; i < argc; i++)
    printf ("Ficheros que se enviaran %s\n", argv[i]);

  printf ("\n\n");
  return 0;
}

El código se parece un poco al ejemplo del manual de getopt(), pero con algún que otro extra (ya sabéis que me gusta poner un toque personal a estas cosas). Lo importante que getopt() está en un bucle, y recibe como argumento argv, argc y un listado de argumentos. En dicho listado veréis letras con las diferentes opciones, algunas acompañadas de dos puntos (:) y otras de dos dos puntos (::), el primer caso es para los parámetros que necesitan un argumento (-o salida) y el segundo para los que el argumento es voluntario (-p / -pclave).

Tenemos, de forma especial, el parámetro -v (verbose), que modifica la variable verbose para ponerla a uno. Se dice que es un argumento que acciona un flag, ya que lo único que hace es activar una opción.

Los argumentos obligatorios debemos controlarlos nosotros, podríamos utilizar variables, tal y como hacemos con los flags para especificar los argumentos obligatorios que se han especificado, o incluso utilizar operaciones de bit sobre un entero para hacerlo de manera más rápida y ocupar menos memoria.

Por último, tenemos que saber que getopt() recoloca aquellos argumentos que no están asociados a ninguna opción (-p, -P, -u, -h) y los pone todos al final, de manera que podamos utilizar el último bucle:

1
2
  for (i = optind; i < argc; i++)
    printf ("Ficheros que se enviaran %s\n", argv[i]);

para recorrer todos los argumentos huérfanos. Podemos basarnos, como ejemplo, en cp, en el que podemos especificar muchos archivos de origen y finalmente un directorio de destino.

Un caso especial

¿Os habéis preguntado alguna vez qué pasa cuando queremos copiar, eliminar, mover, o lo que sea, un archivo que empieza por – (guión) ?
Si lo pensáis, los parámetros, normalmente también empiezan con el mismo guión, ¿cómo puede saber cp, por ejemplo, qué es parámetro y qué es el archivo?

Para eso, tenemos un parámetro especial, –– (dos guiones, WordPress muchas veces me traiciona y pone un guión muy largo), si ponemos dos guiones delante del nombre de archivo podremos escribirlo sin problema. Por ejemplo:

$ rm –– –archivo_que_empieza_por_guion

Pues bien, sólo debemos saber que getopt() también se encarga de gestionar esto.

Próxima entrega

Podemos hacer más cosas, esto es sólo una introducción, estad atentos a la página de Facebook, o Twitter, o entrad en unos días para ver más novedades.

Foto: Tom Eversley (isorepublic)

También podría interesarte....

Leave a Reply