getopt(3) und optind

PhysChemist

Well-Known Member
Hallo,

ich habe ein Problem mit der Inkrementierung von optint mit getopt(3) unter OpenBSD 3.7.

Ich habe ein Programm, bei dem die ersten Kommandozeilen-Argumente immer gleichartig sind, zum Schluss aber einige spezielle kommen.

Eigentlich wollte ich zweimal getopt drüber laufen lassen, und beim zweiten Anlauf nur die noch nicht behandelten Argumente übergeben leider verstehe ich den Inkrementierungsalgoithmus von optint nicht

wenn ich
Code:
while(-1 != (ch=getopt(argc, argv, "f:")))
{
  switch(ch)
  {
    ....
    case '?'
      whileausbruch
    break;
    ....
}
aufrufe
und mein Programm ala
Code:
myProg -f foo -g bar
aufrufe ist optint 4, mit
Code:
myProg -f foo -gbar
jedoch 3

was ich als sehr störend empfinde, da ich getopt nach dem zurücksetzen von optind und optreset
via
Code:
getopt(argc-optindSav+1,argv+optindSav-1,"g:")
aufrufen wollte.

Bin für alle Tipps dankbar
Cu
PhysChemist
 
Vielleicht verstehe ich das Problem nicht ganz, aber wenn es dir darum geht, die Werte von "-f" und "-g" auszulesen, solltest du dir mal den folgenden Quelltext mal ansehen:

Code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void usage(void);

int
main(int argc, char *argv[])
{
	extern char *optarg;
	extern int optind;

	int ch;
	char *fvalue, *gvalue;

	fvalue = gvalue = NULL;
	while ((ch = getopt(argc, argv, "f:g:")) != -1)
		switch (ch) {
			case 'f':
				fvalue = optarg;
				break;
			case 'g':
				gvalue = optarg;
				break;
			default:
				usage();
		}
	argc -= optind;
	argv += optind;

	if (argc)
		usage();

	if (fvalue != NULL)
		printf("-f: %s\n", fvalue);
	if (gvalue != NULL)
		printf("-g: %s\n", gvalue);
	exit(0);
}

void
usage(void)
{
	fprintf(stderr, "usage: example [-f value] [-g value]\n");
	exit(1);
}

Ein solches Beispiel kannst du auch in der getopt(3)-Manualseite finden.
 
Hallo,

soweit wie Du das geschrieben hast, ist mir das Ganze auch klar.

Ich habe aber ein Programm, von dem es unterschiedliche Varianten gibt. Ein Teil der Kommandozeilen-Parameter sind gleich, diese sollen am Anfang stehen. Am Ende der Kommandozeile kommen dann noch einige spefische Paramete, so daß gültige Aufrufe
Code:
myprog_1 -f foo -g bar
und
Code:
myprog_2 -f foo -h bar
sind.

Also hatte ich mir gedacht, in einem allgemeinen Modul wird der Parameter f behandelt und anschließend eine Programmspeziefische Routine cmdParseOther(int argc, char *argv[]) aufzurufen, die weiß, ob g oder h gültig sind.

Momentan habe ich mir so geholfen, daß diese Routine auch nochmal f als gültigen Parameter ansieht, mit den jedoch nichts anstellt. Das ist aber nicht schön, weil ich so ständig in allen Modulen Änderungen vornehmen muss, wenn sich im allgemeinen Teil die Parameter ändern.

Mein Wunsch wäre es (eigentlich ohne getopt neu zu implementieren), den an cmdParseOther() übergebenen Vektor nur die noch nicht behandelten Argumente enthalten zu lassen, wozu ich aber wissen muss, welchen Wert optind annimmt

Gruss
PhysChemist
 
Du sprichst von unterschiedlichen Varianten bei gleichem Code ... hm, wie wäre dann sowas hier:

Code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void usage(void);

int
main(int argc, char *argv[])
{
	extern char *optarg;
	extern int optind;

	int ch;
	char *value, *fvalue;

	value = fvalue = NULL;
	while ((ch = getopt(argc, argv, "f:g:h:")) != -1)
		switch (ch) {
			case 'f':
				fvalue = optarg;
				break;
#ifdef VARIANTE1
			case 'g':
				value = optarg;
				break;
#else
			case 'h':
				value = optarg;
				break;
#endif
			default:
				usage();
		}
	argc -= optind;
	argv += optind;

	if (argc)
		usage();

	if (fvalue != NULL)
		printf("-f: %s\n", fvalue);
	if (value != NULL) {
#ifdef VARIANTE1
		printf("-g: ");
#else
		printf("-h: ");
#endif
		printf("%s\n", value);
	}
	exit(0);
}

void
usage(void)
{
        extern char *__progname;

        fprintf(stderr, "usage: %s [-f value] ", __progname);
#ifdef VARIANTE1
        fprintf(stderr, "[-g value]\n");
#else
        fprintf(stderr, "[-h value]\n");
#endif
	exit(1);
}

Dann gibst du für die eine Variante beim C-Compiler "-DVARIANTE1" mit an, bei der anderen kannst du es weglassen.
 
Zuletzt bearbeitet:
Code:
	while ((ch = getopt(argc, argv, "f:g:h:")) != -1)

Ich verstehe das Problem noch nicht ganz, aber wenn die Präprozessorlösung die richtige ist, dann sollte auch getopt() bedingt übersetzt werden mit jeweils anderen Argumenten.
So wie's im Moment aussieht werden falsche Parameter stillschweigend ignoriert. Ist das Programm z.B. mit -DVARIANTE1 übersetzt worden sollte der User -h nicht angeben dürfen. Tut er es trotzdem so wird die Option einfach ignoriert.

Code:
		switch (ch) {
			case 'f':
				fvalue = optarg;
				break;
#ifdef VARIANTE1
			case 'g':
				value = optarg;
				break;
#else
			case 'h':
				value = optarg;
				break;
#endif
			default:
				usage();
		}
 
Vincent, das Programm funktioniert soweit wie gedacht:

Code:
$ gcc -o test test.c
$ ./test -g 1
usage: test [-f value] [-h value]
$ gcc -DVARIANTE1 -o test test.c
$ ./test -h 1
usage: test [-f value] [-g value]
$_

Wenn man möchte, dann wäre es selbstverständlich kein Problem, getopt() noch unterschiedlich aufzurufen, aber es stört einfach nicht.

Wenn ich das Programm mit ,-h' aufrufe, dann wird getopt() - wie du ja richtig gesagt hast - feststellen, dass er auf ,h' achten soll. Dementsprechend wird die Funktion getopt() den char 'h' zurückgeben. 'h' wird somit in ch gespeichert. Diese Variable wird dann mit switch() abgefragt. In der Abfrage taucht bei VARIANTE1 aber kein ,h' auf, somit fällt er in dem Fall auf default zurück. Und somit ruft er usage() auf.

An dem Programm ist allenfalls der Stil zu bemängeln, da hast du Recht. Aber es ist keinesfalls ein Fehler!

Wenn du es genau nehmen willst, dann müsste auch jede switch-Anweisung, die nach getopt folgt, einen Eintrag für ,?' haben, da das zurückgegeben wird, wenn ein unbekannter Wert angegeben wurde (das machen die meisten Programme auch, aber wie gesagt: nur die meisten).

OpenBSDs hostname macht es nicht, FreeBSDs hostname schon. Oder soll man jetzt sagen, dass OpenBSDs nun auch falsch ist? ;)
 
Ist ja ziemlich haesslich. Was spricht dagegen erstmal alle Parameter zu parsen und zu speichern. Und nachdem getopt fertig ist, zu pruefen, ob alle Parameter in der richtigen Konstellation vorhanden sind?
 
MrFixit, wie würdest du denn die richtigen Parameter überprüfen? An zumindest einer Stelle muss das Programm ja wissen, ob das ,h'-Flag oder das ,g'-Flag erlaubt ist. Man könnte zumindest immer sagen, dass "hflag && gflag" falsch ist, aber woher wissen, ob man nun das ,g'- oder das ,h'-Flag haben will?

Ich wüsste nur eine Möglichkeit, das ohne Präprozessor zu machen: Abfragen von argv[0] (bzw. __progname), was denn nun da sein soll. Das wiederum würde aber vermutlich der Ausgangssituation nicht entsprechen. Genauso wie das Unterteilen in mehrere getopt()-Aufrufe (oder als Alternative eine andere Implementierung).

Es würde mich sehr interessieren, wie du das lösen würdest. Schließlich läuft das mit chgrp/chmod/chown unter OBSD auch über argv[0].
 
Verstehe ich das richtig, dass entweder -h oder -g angegeben wird, aber nicht beides zugleich. Und das ist auch schon alles?

Dann wuerde ich sowas machen (bzw. habe es schon so gemacht). getopt(3) parsed _beide_ und setzt jeweils ein Flag. Ist das Flag des anderen gesetzt, gibts usage() und exit(...)

Pseudocode:
bool gflag = hlfag = false;
while .. getopt("g:h:")
case h;
if (gflag)
print "nur -g oder -h"
hflag = true;
case g:
if (hflag)
print "nur -g oder -h"
gflag = true;


Sicherlich auch nicht huebsch, aber wenn du spaeter neue Optionen einbauen willst, muesstest du getopt(3) an *zwei* Stellen aendern. Ausserdem tut das ganze so in einem Binary, den Praeprozessor fuer sowas bitte nicht nehmen.

hth
 
Was ist denn so schlimm an der Nutzung des Präprozessors für solche Aufgaben?

Ich weiß, gängige Praxis ist es, ihn für Includedateien zu nutzen, aber es kommt meines Erachtens oft vor, dass er eben auch für Codeteile genutzt wird.

Jetzt speziell in diesem Fall finde ich im Nachhinein deine Lösung auch übersichtlicher.

Meinst du mit "für sowas" jetzt speziell diesen Fall oder generell die Nutzung der Präprozessordirektiven für den Quelltext? Ich verweise da einfach nochmal auf die Quelltexte der BSD-Systeme ... Das soll nicht heißen, dass ich sie als Antwort auf alles nutzen will, aber es wird sicherlich gute Gründe geben, dass sie diese nutzen (auch wenn es dann meistens um Funktionalitäten geht, die vielleicht nicht vorliegen, und mit #include dann hinzugefügt werden [+ Codeteil]).
 
Hallo,

Also zuerst will ich noch mal mein Problem genauer umfassen:
Ich schreibe an ein Paar Simulationsroutinen, bei denen ein großer Teil des Codes gleich ist und folglich auch wiederverwendet wird. In diesem gemeinsamen Teil gibt es solche Parameter, wie Schrittweiten, Ausgabeformat etc., die via Commandozeilen-Option übergeben werden.

Im restlichen Teil gibt es modellspezifische Parameter, die ich auch per Commandozeile übergebe, wie die jedoch aussehen und heisen, stellt sich erst heraus, wenn mir ein Modell nicht mehr gefällt und es erweitert oder geändert werden muss.


Nach mehrerem Umschreiben habe ich mein Problem jetzt so gelöst, daß in einem Modul alle gemeinsamen Optionen verarbeitet werden, in den Modellspezifischen Routinen wird noch einmal die gesamte Commandozeile abgeackert, und bei gemeinsamen Parameter einfach nichts gemacht

Stark vereinfacht sieht das so jetzt so aus:

Gemeinsamer Code:
Code:
...
while (1)
{
  ch = getopt(argc, argv, ":g:");
  if('g' == ch)
  {
     /* mache dies */
     continue;
  }
  break;
}
lCmdParse(argc, argv);
...

modellspezisischer Code:
Code:
...
int lCmdParse(int argc, char *argv[])
{
  while (1)
  {
    ch = getopt(argc, argv, ":g:h:");
    if('g' == ch) continue;
    if('h' == ch)
    {
       /* mache jenes */
      continue;
    }
    return 0;
  }
}

Wenn das noch komplizierter wird, werde ich wohl das ganze per Liste und Funktionszeiger lösen.


Gruß
PhysChemist
 
Paldium schrieb:
Was ist denn so schlimm an der Nutzung des Präprozessors für solche Aufgaben?

Weil das eine Entscheidung ist, die zur Runtime getroffen werden sollte, nicht zur Compiletime.

So wie ich das verstanden habe, will er ja beide funktionsweise in einem Binary. Mit dem Praeprozessor ist also kein Blumentopf zu gewinnen.

Nur portabilitaetsbedingte Bedingungen sollten in den Praeprozessor. Den Rest sollte das Programm dann zur Laufzeit auskaspern. IHMO, YMMV.
 
Zurück
Oben