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
Pingback: Cómo gestionar los parámetros de un programa en C (parte 2: parámetros largos) | PlanetaLibre /
Pingback: Procesar argumentos de entrada en nuestros shell scripts con getopt – Poesía Binaria /