Poesía Binaria

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

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:

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....