54 votes

Getopt ne analyse pas les arguments optionnels des paramètres

En C, getopt_long ne analyse pas les arguments facultatifs des paramètres de la ligne de commande.

Lorsque j'exécute le programme, l'argument facultatif n'est pas reconnu comme dans l'exemple ci-dessous.

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

Voici le code de test.

#include 
#include 

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("Option inconnue\n"); break;
        }
    } 
    return 0;
}

2 votes

Je documente ceci ici avec la réponse, donc d'autres personnes n'ont pas besoin de se cogner la tête contre le mur.

101voto

hayalci Points 2161

Bien que cela ne soit pas mentionné dans la documentation de glibc ou dans la page de manuel de getopt, les arguments optionnels aux paramètres de ligne de commande de style long nécessitent un 'signe égal' (=). L'espace séparant l'argument optionnel du paramètre ne fonctionne pas.

Un exemple d'exécution avec le code de test :

$ ./respond --praise John
Bravo à John
$ ./respond --praise=John
Bravo à John
$ ./respond --blame John
Tu es nul !
$ ./respond --blame=John
Vous êtes nul, John !

2 votes

Notez que le module Getopt::Long de Perl N'A PAS la même exigence. Boost.Program_options le fait.

19 votes

Wow, c'est nul. J'ai rencontré le même problème avec getopt() normal et en utilisant une optstring "a ::", optarg ne serait défini que si vous n'avez AUCUN espace entre l'option et l'argument comme '-afoo'

4 votes

Au moins pour mes options courtes comme -a ou -a=300, j'ai dû ajouter un if (optarg[0] == '=') {memmove(optarg, optarg+1, strlen(optarg));} pour supprimer le =. Sinon, j'aurais toujours eu =300 dans optarg. Ou j'aurais dû l'appeler de cette façon : -a300 - ce qui est assez moche je pense. Merci pour votre réponse de toute façon, elle m'a beaucoup aidé !

18voto

Brian Vandenberg Points 1042

La page de manuel ne le documente certainement pas très bien, mais le code source aide un peu.

En bref : vous êtes censé faire quelque chose comme ce qui suit (bien que cela puisse être un peu trop pédant) :

if(   !optarg
   && optind < argc // assurez-vous que optind est valide
   && NULL != argv[optind] // assurez-vous que ce n'est pas une chaîne nulle
   && '\0' != argv[optind][0] // ... ou une chaîne vide
   && '-' != argv[optind][0] // ... ou une autre option
  ) {
  // mettez à jour optind pour que l'invocation suivante de getopt_long saute argv[optind]
  my_optarg = argv[optind++];
}
/* ... */

Parmi les commentaires précédant _getopt_internal :

...

Si getopt trouve un autre caractère d'option, il renvoie ce caractère, mettant à jour optind et nextchar afin que l'appel suivant à getopt puisse reprendre l'analyse avec le caractère d'option suivant ou l'élément ARGV.

S'il n'y a plus de caractères d'option, getopt renvoie -1. Ensuite, optind est l'indice dans ARGV du premier élément ARGV qui n'est pas une option. (Les éléments ARGV ont été permutés de telle sorte que ceux qui ne sont pas des options viennent maintenant à la fin.) <-- une note de ma part : si le 3e argument de getopt_long commence par un tiret, argv ne sera pas permuté

...

Si un caractère dans OPTSTRING est suivi de deux-points, cela signifie qu'il veut un argument, donc le texte suivant dans le même élément ARGV, ou le texte de l'élément ARGV suivant, est renvoyé dans optarg. Deux deux-points signifient une option qui veut un argument facultatif ; s'il y a du texte dans l'élément ARGV actuel, il est renvoyé dans optarg, sinon optarg est défini à zéro.

...

... bien que vous deviez faire une lecture entre les lignes. Ce qui suit fait ce que vous voulez :

#include 
#include 

int main(int argc, char* argv[] ) {
  int getopt_ret;
  int option_index;
  static struct option long_options[] = {
      {"louange",  required_argument, 0, 'p'}
    , {"blâme",  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:
        // gérer les arguments non-options ici si vous mettez un `-`
        // au début du 3e argument de getopt_long
        break;
      case 'p':
        printf("Bravo à %s\n", optarg); break;
      case 'b':
        if(   !optarg
           && NULL != argv[optind]
           && '-' != argv[optind][0] ) {
          // C'est ce qui le rend possible ; si `optarg` n'est pas défini
          // et que argv[optind] ne ressemble pas à une autre option,
          // alors supposez que c'est notre paramètre et modifiez ouvertement optind
          // pour compenser.
          //
          // Je ne suis pas très fan de la façon dont cela est fait dans la API getopt,
          // mais si vous regardez la page de manuel, elle documente
          // l'existence de `optarg`, `optind`, etc, et ils ne sont pas marqués const
          // -- ce qui implique qu'ils s'attendent et ont l'intention que vous
          // les modifiez si nécessaire.
          tmp_optarg = argv[optind++];
        }
        printf( "Tu es nul" );
        if (tmp_optarg) {
          printf (", %s !\n", tmp_optarg);
        } else {
          printf (" !\n");
        }
        break;
      case '?':
        printf("Option inconnue\n");
        break;
      default:
        printf( "Inconnu : getopt_ret == %d\n", getopt_ret );
        break;
    }
  }
  return 0;
}

1 votes

Cela a vraiment bien fonctionné, merci. Pas sûr d'où vous avez obtenu optindex; cela s'appelle (extern int) optind pour moi.

1 votes

Il y a une erreur dans le deuxième exemple de code, il devrait être optind au lieu de optindex.

0 votes

Il semble que le code doit maintenir optindex. Sinon, il pointera toujours vers 0. Nous devons avancer optindex pour chaque option.

3voto

larsewi Points 46

J'ai récemment rencontré ce problème moi-même. J'ai trouvé une solution similaire à celle suggérée par Brian Vandenberg et Haystack. Mais pour améliorer la lisibilité et éviter la duplication du code, vous pouvez tout envelopper dans une macro comme ci-dessous :

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

La macro peut être utilisée comme ceci :

case 'o': // option avec argument facultatif
    if (OPTIONAL_ARGUMENT_IS_PRESENT)
    {
        // La  poignée est présente
    }
    else
    {
        // La  poignée n'est pas présente
    }
    break;

Si cela vous intéresse, vous pouvez en savoir plus sur le fonctionnement de cette solution dans un article de blog que j'ai écrit : https://cfengine.com/blog/2021/optional-arguments-with-getopt-long/

Cette solution est testée et - au moment de la rédaction de cet article - actuellement utilisée dans CFEngine.

1voto

J'ai également rencontré le même problème et je suis venu ici. Ensuite, j'ai réalisé cela. Vous n'avez pas beaucoup de cas d'utilisation de "optional_argument". Si une option est requise, vous la vérifiez à partir de la logique du programme, si une option est facultative, alors vous n'avez rien à faire car au niveau de getopt, toutes les options sont facultatives, elles ne sont pas obligatoires, donc il n'y a aucun cas d'utilisation de "optional_argument". J'espère que cela vous aidera.

ps: pour l'exemple ci-dessus, je pense que les options correctes sont --praise --praise-name "nom" --blame --blame-name "nom"

1 votes

Vous n'avez pas vraiment de cas d'utilisation de "optional_argument" - Oh, mais si. Imaginez que nous avons $ whip --spank user5081924 et $ whip --spank. Le premier gifle un utilisateur spécifique, le second gifle l'appelant. Sans l'argument optionnel, vous seriez obligé de vous gifler vous-même ou de gifler uniquement les autres. (On pourrait avoir --spank-self et --spank-other je suppose, mais pourquoi compliquer.)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X