Publi

Cómo gestionar los parámetros de un programa en C (parte 2: parámetros largos)

photo-1415348667647-9e035495cfb7_r

Hace unos días publiqué Cómo gestionar los parámetros de nuestros programas con getopt en C como una introducción a la gestión de los argumentos desde nuestros programas en C. De esta forma podremos hacer nuestros programas más flexibles aceptando una entrada del usuario en forma de parámetros y así modificar el comportamiento.

Antes de nada: WordPress cuando ve dos guiones seguidos (- y -) lo interpreta como un guión largo. En ocasiones viene bien y no molesta, pero en un post como este sí que perjudica más que beneficia, puedo desactivar el filtro, pero tengo mis dudas de que haya un post en el que lo quiera así. Tendré que estudiar la opción de desactivarlo sólo para un post determinado.

Para ello vimos getopt(), que nos permitía introducir opciones cortas (-v, -a, -j…) incluso con un parámetro asociado, por lo que podíamos empezar a introducir claves y valores en nuestros programas.

Hoy vemos un ejemplo de getopt_long() (también parecido al ejemplo del manual y derivado del anterior de getopt()) con algunos extras curiosos intentando contemplar varias opciones. El programa simula una conexión a un servicio, para ello especificamos host, usuario, contraseña y algunas cosas más, aunque no conecta ni nada, sólo es hipotético.

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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/**
*************************************************************
* @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.3\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, --help \t\t\tPresenta esta ayuda en pantalla.\n");
  fprintf (stderr, "  -v, --verbose\t\t\t\tActiva el modo verbose.\n");
  fprintf (stderr, "  -u usuario, --user=usuario\t\tEspecifica el nombre de usuario.\n");
  fprintf (stderr, "  -p [password], --pass[=password]\tIntroduce la contraseña.\n");
  fprintf (stderr, "  -P puerto, --port=port\t\tEspecifica el puerto de conexión\n");
  fprintf (stderr, "  --output=xml/json\t\t\tEspecifica el tipo de salida que quieres generar\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;
  int oindex;
  char output[10]="none";
  /* variables globales de getopt */
  extern char* optarg;
  extern int optind;
  extern int optopt;
  extern int opterr;
  opterr = 0;
  struct option cli_options[] = {
    {"verbose", 0, &verbose, 64},
    {"user", 1, NULL, 'u'},
    {"pass", 2, NULL, 'p'},
    {"port", 1, NULL, 'P'},
    {"host", 1, NULL, 'h'},
    {"help", 0, NULL, 'H'},
    {"output", 1, NULL, 0},
    {NULL, 0, NULL, 0}
  };
  while ((c = getopt_long (argc, argv, "Hvu:p::P:h:", cli_options, &oindex)) != -1)
    {
      printf ("c = %d(%c) index = %d\n", c, c, oindex);
    switch (c)
      {
      case 0:
    /* Aquí entramos cuando una opción larga no tiene una opción corta
       para procesarse. */

    if (strncmp(cli_options[oindex].name, "output", 10)==0)
      {
        if ( (strncmp(optarg, "json", 10)==0) || (strncmp(optarg, "xml", 10)==0) )
          strncpy(output, optarg, 10);
        else
          {
        fprintf (stderr, "El parámetro output es incorrecto.\n");
        help(argv[0]);
          }
      }
    break;
      case 'v':
    verbose=1;
        break;
      case 'u':
    user = optarg;
        break;
      case 'p':
    if (!optarg)
      pass = pide_password();
    else
      {
        pass = malloc(strnlen(optarg, 128)+1);
        strncpy(pass, optarg, 128);
        /* Llenamos el password de asteriscos para que no sea visible
           con ps, por ejemplo. */

        for (l=0; l<strnlen(optarg, 128); ++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);
  printf ("Output: %s\n", output);

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

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

En este caso tenemos opciones largas, es decir en lugar de (-v, -h, -p, etc) podemos utilizar ademas (–verbose, –host, –pass, etc). Es más, podemos utilizar los dos tipos, incluso mezclados:

$ ./connect –verbose -h localhost

Incluso podemos tener opciones como (–output) que no tienen su equivalente en opción corta. Es decir, sólo tienen opción larga, y está bien, porque nos podemos quedar sin abecedario para las opciones cortas y seguir necesitando algunas más.

¿Si ya tenemos opciones cortas, con las que se teclea menos, para qué queremos las largas?
Sobre todo, porque son más fáciles de recordar, en el ejemplo, tenemos dos opciones -p (para el password) y -P (para el puerto), pero en opciones largas tenemos (–pass y –port) lo que hace que sea más fácil recordar cada una de ellas, imaginad cuando tengamos un programa con muchas opciones… puede ser muy confuso.
Además, como decía antes, puede que nos quedemos sin letras para las opciones cortas (tenemos mayúsculas, minúsculas y números, pero aún así puede que nos quedemos cortos, en ese caso, tenemos las opciones largas, que al tener más de una letra habrá muchas más combinaciones.

Veo un struct muy raro para definir las opciones, ¿cómo se hace?
El struct donde se definen las opciones largas es el siguiente:

1
2
3
4
5
6
7
8
9
10
  struct option cli_options[] = {
    {"verbose", 0, &verbose, 64},
    {"user", 1, NULL, 'u'},
    {"pass", 2, NULL, 'p'},
    {"port", 1, NULL, 'P'},
    {"host", 1, NULL, 'h'},
    {"help", 0, NULL, 'H'},
    {"output", 1, NULL, 0},
    {NULL, 0, NULL, 0}
  };

Es un array de structs y cada struct tiene cuatro variabes:

  • nombre de la opción
  • ¿tiene argumento? Vale 0 (o no_argument) si no tiene argumento, es decir, si no queremos darle un valor a esa opción. Valdrá 1 (o required_argument) si es necesario ese argumento, como cuando en las opciones cortas ponemos dos puntos (:), o 2 (o optional_argument) si podemos incluir el argumento o no, vamos que sea opcional, como cuando en las opciones cortas ponemos dos signos de dos puntos (::). Para este último caso, el argumento deberá ser introducido desde línea de comandos con el signo igual:

    $ ./connect –pass=miContraseña

  • La tercera variable:
    • Si vale 0 (o NULL), getopt_long() devolverá el valor de la cuarta variable. Por ejemplo, para “output”, estoy devolviendo 0, lo que significa que cuando vea ese argumento en la línea de comandos, dentro del switch, lo procesaré con la opción 0. Pero para “host”, estoy devolviendo directamente ‘h’, por lo que haré como si me hubiera entrado la opción corta ‘h’ y procesaré tanto –host como -h de la misma forma.
    • Si tiene otro valor, deberá ser una dirección de memoria. Y en esa dirección de memoria se escribirá el valor de la cuarta variable. Como en el ejemplo, tenemos (&verbose), es decir, metemos la dirección de memoria de esa variable y luego un valor en la cuarta variable. De esta forma, cuando hagamos:

      $ ./connect –verbose

      a la variable verbose (de tipo int) le daremos el valor 64. Es un ejemplo, en este caso para distinguir entre –verbose y -v.

  • La cuarta variable está explicada junto con la tercera

Como con getopt() el tema de opciones obligatorias o control de errores en los argumentos, es decir, que una opción reciba un valor no válido, etc debemos controlarlo nosotros. Por ejemplo, –output sólo acepta json o xml. En este caso he decidido comprobarlo con strncmp(), con una n en medio, igual que strncpy() y similares, para que el programa tenga cierta tolerancia a fallos, valores muy grandes que no caben en nuestras variables, etc.

Aquí he querido incluir varios tipos de opciones
Foto: Maya Karmon

También podría interesarte....

Leave a Reply