Argumente einfach parsen unter Windows

Hallo an alle!

Diese Seite beschreibt einen Quick 'n' Dirty Hack, also bitte seid nicht böse wenn sich ein paar kleinere Fehler eingeschlichen haben.

Dann leg ich einfach mal los.

Worum gehts hier eigentlich?

Falls ihr schon mal ein paar kleinere Programme geschrieben habt werdet ihr früher oder später an das Problem der Übergabeparameter gestoßen sein. Die Übergabeparameter zu parsen, verarbeiten und Fehler zu entdecken kann nämlich schnell ziemlich nervig werden, und vor allem ziemlich Aufwändig und Fehleranfällig.

Was ist fat-lobyte's Patentlösung für solche Probleme? Natürlich - einfach ne externe Bibliothek verwenden ;-)

Die Lösung - getopt()

Im Fall der Übergabeparameter verwende ich die magische „getopt“ Funktion (genauer gesagt getopt_long), die in fast jeder Unixanwendung für die Verarbeitung der Parameter zuständig ist. Unter Linux machen wir oben im Programm einfach mal den hier:

#include <unistd.h> 

Wenn ihr unter Windoof unterwegs seid, siehts da natürlich düster aus - kein getopt() weit und breit, und nichtmal eine Alternative in Sicht. ABER es gibt einen Ausweg!

Die GNU C-Bibliothek (auch glibc genannt) ist eine erstaunlich portable Bibliothek - sie unterstützt die seltsamsten und ältesten C Dialekte (und Abarten). Wie der Zufall so will kompiliert der getopt() Teil der glibc selbst auf dem Compiler des Erzfeindes des GNU - auf dem Visual Studio. Diese Entdeckung hat mir ziemlich viel Zeit und Nerven beim Argumenteverarbeiten gespart. Nun will ich euch daran Teilhaben lassen:

Als erstes ziehen wir uns die Aktuelle glibc auf die Platte, da zur Zeit des Verfassens dieses Artikels Version 2.9. Eigentlich muss das ganz und gar nicht die Aktuelle sein, die Funktion ist eine Kernfunktion jeder UNIX-Distribution und somit „überall“ vorhanden. Zur Einfachheit werde ich jetzt aber nur die Version 2.9 verwenden.

Die glibc-2.9 gibts hier: http://ftp.gnu.org/gnu/glibc/glibc-2.9.tar.gz

Wir entpacken jetzt das Archiv irgendwohin, und ziehen uns dort ein paar Dateien raus. Hier sind sie relativ zum glibc Verzeichnis aufgeschrieben:

  • posix/getopt.h
  • posix/getopt_int.h
  • posix/getopt.c
  • posix/getopt1.c

Diese Dateien schmeißt ihr nun einfach in euer Projektverzeichnis (oder wo auch immer ihr den Qellcode für euer Programm habt).

Ich hab die Dateien hier auch nochmal hochgeladen: getopt.c getopt.h getopt1.c getopt_int.h

getopt.c patchen

Ihr werdet euch vielleicht denken „Wie um himmels Willen soll ich 2 Dateien die ich einfach irgendwie aus der glibc rausgezogen habe unter Windoof kompiliert bekommen??“. Tja, die Antwort ist überraschend einfach, ihr müsst folgendes machen:

  • Nichts.
  • Naja gut, ein bisschen was schon:

Die Version aus der glibc umklammert jeden String mit dem Makro _. Ja ihr habt richtig gelesen. Das define sieht so aus:

# include "gettext.h"
# define _(msgid) gettext (msgid)

Dumm gelaufen, gettext.h gibts nicht. Deswegen öffnen wir die Datei getopt.c, kommentieren das #include auf Zeile 74 aus und ersetzen das Makro auf Zeile 75 mit

# define _(msgid) (msgid)

(das bedeutet das was beim Makro reinkommt kommt gleich wieder raus).

Für diejenigen, die mit patch umgehen können, hier der Patch dazu: gettext.patch

Jetzt noch „getopt.h“ dort inkludieren wo es gebraucht wird, und schon gehts los! Ihr habts geschafft, und könnt getopt() jetzt verwenden. Infos und Beispielprogramme gibts hier: http://www.gnu.org/s/libc/manual/html_node/Getopt.html

Meine program_options Klasse

Da ihr das jetzt alle brav gelesen habts gibts noch ein paar goodies:

Hier eine kleine Klasse die die Optionen an ein Programm verarbeitet und in einem Struct (also in der Klasse selbst) abspeichert:

program_options.cpp program_options.h

Benutzen könnte man diese Klasse so:

#include <iostream>
 
#include "program_options.h"
 
int main(int argc, char* argv[])
{
    ProgramOptions op(argc, argv);
    if (!op)
    {
        std::cerr<<"Invalid usage!\n";
        return 1;
    }
 
    std::cout<<"We are treating the file \""<<op.filename<<"\"\n";
    std::cout<<"A is on: "<<op.a_flag<<'\n';
 
    if (op.some_option.empty())
        std::cout<<"Some option not specified\n";
    else
        std::cout<<"Some option is \""<<op.some_option<<"\"\n";
 
    if (op.another_option.empty())
        std::cout<<"Another option not specified\n";
    else
        std::cout<<"Another option is \""<<op.another_option<<"\"\n";
 
    return 0;
}

Eine Beispielsession könnte dann so aussehen:

 # Einfach mal ohne Dateinamen:
>testgetopt.exe
Missing filename
Invalid usage!
 
 # Richtig:
>testgetopt.exe test
We are treating the file "test"
A is on: 0
Some option not specified
Another option not specified
 
 # Mit nicht bekannter Option:
>testgetopt.exe --unknown-option test
\testgetopt.exe: unrecognized option '--unknown-option'
Invalid usage!
 
 # Mit Flag -f
>testgetopt.exe -f testfile.txt
We are treating the file "testfile.txt"
A is on: 1
Some option not specified
Another option not specified
 
 # alle Optionen angegeben
>testgetopt.exe -f --some-option arg1 --another-option arg2 testfile.taxt
We are treating the file "testfile.txt"
A is on: 1
Some option is "arg1"
Another option is "arg2"
 
 
 # --another-option kurzgefasst
>testgetopt.exe -f --some-option arg1 -a=33 testfile.txt
We are treating the file "testfile.txt"
A is on: 1
Some option is "arg1"
Another option is "=33"
 
 # oder alles kurz geschrieben
>testgetopt.exe -f -s arg1 -a 33 testfile.txt
We are treating the file "testfile.txt"
A is on: 1
Some option is "arg1"
Another option is "33"
 
 # Mit = dazwischen, oder auch ohne Leerzeichen
>testgetopt.exe -f --some-option=222 -a33 testfile.txt
We are treating the file "testfile.txt"
A is on: 1
Some option is "222"
Another option is "33"
 
 
 # Zu viele Argumente
>testgetopt.exe testfile.txt testfile2.txt
Extranous Arguments
Invalid usage!
 
 # Argumente die mit - beginnen, können nach --- angegeben werden:
>testgetopt.exe -f -aAnother --- --file-with-leading-hyphens.txt
We are treating the file "---file-with-leading-hyphens.txt"
A is on: 1
Some option not specified
Another option is "Another"

So einfach kanns gehen - einfach ein paar Dateien inkludieren, und alle Probleme mit den Argumenten sind gelöst!