getopt no analiza argumentos opcionales a parámetros

6 minutos de lectura

avatar de usuario
Hayalci

En C, getopt_long no analiza los argumentos opcionales de los parámetros de la línea de comandos.

Cuando ejecuto el programa, el argumento opcional no se reconoce como en el ejemplo que se ejecuta a continuación.

$ ./respond --praise John
Kudos to John
$ ./respond --blame John
You suck !
$ ./respond --blame
You suck !

Aquí está el código de prueba.

#include <stdio.h>
#include <getopt.h>

int main(int argc, char ** argv )
{
    int getopt_ret, option_index;
    static struct option long_options[] = {
               {"praise",  required_argument, 0, 'p'},
               {"blame",  optional_argument, 0, 'b'},
               {0, 0, 0, 0}       };
    while (1) {
        getopt_ret = getopt_long( argc, argv, "p:b::",
                                  long_options,  &option_index);
        if (getopt_ret == -1) break;

        switch(getopt_ret)
        {
            case 0: break;
            case 'p':
                printf("Kudos to %s\n", optarg); break;
            case 'b':
                printf("You suck ");
                if (optarg)
                    printf (", %s!\n", optarg);
                else
                    printf ("!\n", optarg);
                break;
            case '?':
                printf("Unknown option\n"); break;
        }
    } 
    return 0;
}

  • Estoy documentando esto aquí con la respuesta, para que otras personas no tengan que golpearse la cabeza contra la pared.

    – Hayalci

    27 de junio de 2009 a las 12:38

avatar de usuario
Hayalci

Aunque no se menciona en la documentación de glibc ni en la página de manual de getopt, los argumentos opcionales para los parámetros de línea de comando de estilo largo requieren el ‘signo igual’ (=). El espacio que separa el argumento opcional del parámetro no funciona.

Un ejemplo ejecutado con el código de prueba:

$ ./respond --praise John
Kudos to John
$ ./respond --praise=John
Kudos to John
$ ./respond --blame John
You suck !
$ ./respond --blame=John
You suck , John!

  • Tenga en cuenta que el módulo Getopt::Long de Perl NO tiene el mismo requisito. Boost.Program_options lo hace.

    – Rob Kennedy

    27 de junio de 2009 a las 15:08

  • Vaya, esto apesta. Me encontré con el mismo problema con getopt () regular y cuando usaba una cadena de opciones “a::”, optarg solo se establecería si tiene CERO espacio entre la opción y el argumento como ‘-afoo’

    – SiegeX

    20 de noviembre de 2009 a las 23:44

  • Al menos para mis opciones cortas como -a o -a=300necesitaba agregar un if (optarg[0] == '=') {memmove(optarg, optarg+1, strlen(optarg));} para despojar a los =. De lo contrario, siempre hubiera tenido =300 en optarg. O hubiera tenido que llamarlo así: -a300 – que es bastante feo, creo. Gracias por tu respuesta de todos modos, me ayudó mucho!

    – mozzbozz

    31 de mayo de 2014 a las 17:27


  • En este caso es mejor no usar argumentos opcionales. ¿Es esto un error o una característica? Ahora tengo ganas de ./respond --blame=glibc.

    – petersohn

    07/03/2015 a las 19:11

  • Quería votar negativamente, pero luego me di cuenta de que su respuesta es excelente; es solo el comportamiento que quiero rechazar. ¡Gracias!

    – lmat – Reincorporar a Mónica

    17 mayo 2019 a las 15:58

avatar de usuario
Brian Vandenberg

La página de manual ciertamente no lo documenta muy bien, pero el código fuente ayuda un poco.

Brevemente: se supone que debes hacer algo como lo siguiente (aunque esto puede ser un poco demasiado pedante):

if(   !optarg
   && optind < argc // make sure optind is valid
   && NULL != argv[optind] // make sure it's not a null string
   && '\0' != argv[optind][0] // ... or an empty string
   && '-' != argv[optind][0] // ... or another option
  ) {
  // update optind so the next getopt_long invocation skips argv[optind]
  my_optarg = argv[optind++];
}
/* ... */

De entre los comentarios anteriores a _getopt_internal:

Si getopt encuentra otro carácter de opción, devuelve ese carácter,
actualizando optind y nextchar para que la próxima llamada a getopt puede reanudar el escaneo con el siguiente carácter de opción o elemento ARGV.

Si no hay más caracteres de opción, getopt devuelve -1. Luego optind es el índice en ARGV del primer elemento ARGV que no es una opción. (Los elementos ARGV se han permutado para que aquellos que no son opciones ahora queden en último lugar). <-- a note from me:
if the 3rd argument to getopt_long starts with a dash, argv will not
be permuted

Si un carácter en OPTSTRING va seguido de dos puntos, eso significa que quiere un argumento, por lo que el siguiente texto en el mismo elemento ARGV, o el texto del siguiente elemento ARGV, se devuelve en optarg. Dos dos puntos significan una opción que quiere un argumento opcional; si hay texto en el elemento ARGV actual, se devuelve en optarg, de lo contrario optarg se pone a cero.

… aunque hay que leer un poco entre líneas. Lo siguiente hace lo que quieres:

#include <stdio.h>
#include <getopt.h>

int main(int argc, char* argv[] ) {
  int getopt_ret;
  int option_index;
  static struct option long_options[] = {
      {"praise",  required_argument, 0, 'p'}
    , {"blame",  optional_argument, 0, 'b'}
    , {0, 0, 0, 0}
  };

  while( -1 != ( getopt_ret = getopt_long(  argc
                                          , argv
                                          , "p:b::"
                                          , long_options
                                          , &option_index) ) ) {
    const char *tmp_optarg = optarg;
    switch( getopt_ret ) {
      case 0: break;
      case 1:
        // handle non-option arguments here if you put a `-`
        // at the beginning of getopt_long's 3rd argument
        break;
      case 'p':
        printf("Kudos to %s\n", optarg); break;
      case 'b':
        if(   !optarg
           && NULL != argv[optind]
           && '-' != argv[optind][0] ) {
          // This is what makes it work; if `optarg` isn't set
          // and argv[optind] doesn't look like another option,
          // then assume it's our parameter and overtly modify optind
          // to compensate.
          //
          // I'm not terribly fond of how this is done in the getopt
          // API, but if you look at the man page it documents the
          // existence of `optarg`, `optind`, etc, and they're
          // not marked const -- implying they expect and intend you
          // to modify them if needed.
          tmp_optarg = argv[optind++];
        }
        printf( "You suck" );
        if (tmp_optarg) {
          printf (", %s!\n", tmp_optarg);
        } else {
          printf ("!\n");
        }
        break;
      case '?':
        printf("Unknown option\n");
        break;
      default:
        printf( "Unknown: getopt_ret == %d\n", getopt_ret );
        break;
    }
  }
  return 0;
}

  • Esto funcionó muy bien, gracias. No estoy seguro de dónde obtuviste optindex; se llama (extern int) optind para mí.

    – apanloco

    24 de diciembre de 2017 a las 22:21

  • Hay un error en el segundo ejemplo de código, debería ser optind en lugar de optindex.

    – Sebastián Schrader

    14 de enero de 2018 a las 2:18


  • Parece que el código debería mantener optindex. De lo contrario, apuntará a 0 siempre. necesitamos avanzar optindex para cada opción.

    – Howard Shane

    16 de abril de 2018 a las 19:03

avatar de usuario
larsewi

Recientemente me encontré con este problema. Llegué a una solución similar a la sugerida por Brian Vandenberg y Haystack. Pero para mejorar la legibilidad y evitar la duplicación de código, puede envolverlo todo en una macro como la siguiente:

#define OPTIONAL_ARGUMENT_IS_PRESENT \
    ((optarg == NULL && optind < argc && argv[optind][0] != '-') \
     ? (bool) (optarg = argv[optind++]) \
     : (optarg != NULL))

La macro se puede utilizar así:

case 'o': // option with optional argument
    if (OPTIONAL_ARGUMENT_IS_PRESENT)
    {
        // Handle is present
    }
    else
    {
        // Handle is not present
    }
    break;

Si está interesado, puede leer más sobre cómo funciona esta solución en una publicación de blog que escribí:
https://cfengine.com/blog/2021/argumentos-opcionales-con-getopt-long/

Esta solución está probada y, en el momento de escribir este artículo, se usa actualmente en CFEngine.

También me encontré con el mismo problema y vine aquí. Entonces me di cuenta de esto. No tienes mucho de un caso de uso de “opcional_argumento” . Si se requiere una opción, verifique desde la lógica del programa, si una opción es opcional, entonces no necesita hacer nada porque en el nivel getopt todas las opciones son opcionales, no son obligatorias, por lo que no hay caso de uso de “argumento_opcional”. Espero que esto ayude.

pd: para el ejemplo anterior, creo que las opciones correctas son –elogio –elogio-nombre “nombre” –culpa –culpa-nombre “nombre”

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad