Von C nach Java, Teil 1: Der schnelle Umstieg von der Kommandozeile aus

Seite 2: Kommandozeile

Inhaltsverzeichnis

Nun sei das Behandeln der Kommandozeile in Java analog der nur grob als Vorlage erläuterten C-Variante detailliert beschrieben. Zunächst das C-Beispiel:

int main(int argc, char *argv[]) {
int i, argCnt=1, optFlag=0, numCmd=0, numPathListItems=0;
char **sCmdList=NULL, **sPathList=NULL, *s, *argStr, *optStr="alpv";

while (--argc) {
argStr=argv[argCnt++];
if (*argStr == '-' && strlen(argStr)>1) {
while (*++argStr) {
if ((s=strchr(optStr,*argStr))==NULL) usage();
optFlag |= 1 << (s-optStr);
}
}
sCmdList=realloc(sCmdList,(1+numCmd)*sizeof(char *));
sCmdList[numCmd++]=argStr;
}
}

Es können Schalter (Switches) und es muss zumindest ein Kommando übergeben werden, nachdem gesucht werden soll. Der String optStr enthält die zulässigen Optionen: ("alpv");.

Die große while-Schleife arbeitet alle "Wörter" der Kommandozeile ab. Falls das erste Zeichen ein "-" ist, handelt es sich um einen Schalter (Switch), und es wird im Folgenden geprüft, welcher es ist. Ziemlich raffiniert (aber analog der C-Manier) ist es, aus der Position des gefundenen Zeichens die Bit-Position einer Flag-Variablen zu berechnen und es in einer "Oder"-Verknüpfung zu setzen. Ist die Option unbekannt, wird das Programm mit einem Benutzungshinweis (usage()) beendet.

Alle Parameter, denen kein "-" vorausgeht, sind Kommandos oder Teile von Dateinamen, nach denen im Anschluss gesucht werden soll. Diese speichert man in einem String Array (char **sCmdList) zwecks späterer Verwendung. Nach dem Parsen der Kommandozeile liegen die zu suchenden Kommandos in <sCmdList> und die Flags in der Variablen <optFlag> vor.

Jetzt zur Java-Variante. Der Autor hat sich hierbei – gleich zu Beginn – für die mehr objektorientierte Alternative entschlossen und lässt das Parsen der Kommandozeile im Konstruktor der Anwendungsklasse laufen. Dementsprechend kurz fällt die main()-Methode aus:

public class JType {
public JType(String[] args) throws Exception {
...
}

public static void main(String[] args) {
try {
new JType(args);
} catch (Exception e) {
e.printStackTrace();
}
}
}

Im Grunde liegt hier ein GerĂĽst vor, mit dem man bequem jedes Kommandozeilen-Tool in Java bauen kann. Es ist lediglich der Name der Anwendungsklasse anzupassen.

Die statischen Methoden einer Klasse sind den klassischen C-Funktionen am ähnlichsten. Mit ihnen lassen sich aus einer Klasse heraus Funktionen – quasi ad hoc – abrufen, die in der Regel eine Variable (Objekt) zurückgeben, ohne dass im aufrufenden Programm eine Instanz dieser Klasse zu erzeugen ist. Einige häufig vorkommende Beispiele listet die Tabelle 1 auf.

Aufruf Klasse Beschreibung
System.exit(0); System Beendet das aktuelle Programm mit dem entsprechenden Statuscode.
System.out.println("Dies ist ein String"); System Gibt einen Text + Newline auf dem Bildschirm aus.
String s=System.getenv("PATH"); System Weist dem String s den Inhalt der Umgebungsvariablen "PATH" zu.



Was passiert hier im Detail? Der statischen Methode main ("statisch" ist eine Methode einer Klasse, wenn sie sich von außen aufrufen lässt und nicht innerhalb einer Instanz der Klasse) wird ein String[]-Objekt args übergeben. Es handelt sich hierbei um ein String-Array, das alle "Wörter" der Kommandozeile enthält. Im Gegensatz zum C-Programm ist aber der Programmname nicht Gegenstand der Kommandozeile, sodass args[0] den ersten Parameter enthält.

Was dem Noch-Java-Neuling sofort ins Auge sticht, ist der "try-catch"-Block. Er fängt alle Ausnahmen (Exceptions), die in der gesamten Programmausführung auftreten können, auf und arbeitet sie dann im catch()-Block ab. Was weiter passiert, obliegt dem Geschmack des Entwicklers. Der Autor hat einfach mal einen sogenannten StackTrace aufgerufen (e.printStackTrace()), der alle Ausnahmebedingungen auflistet und das Debuggen der Anwendung somit stark vereinfacht. Anschließend verlässt der Entwickler das Programm mit dem Exit-Code 1 (System.exit(1)).

new JType(args); ruft das Programm auf. Mit new legt man eine neue Instanz eines Objekts an, in diesem Fall für die eigene Anwendungsklasse JType. Die virtuelle Maschine sucht den Konstruktor dieser Klasse und führt den aus, auf den Anzahl und Typ der übergebenen Argumente passen. Im vorliegenden Programm steht an oberster Stelle die spezielle Methode public JType (String[] args) – der Konstruktor. Er ist in der Regel public (also von außen sichtbar), gibt nichts zurück und trägt den Namen der jeweiligen Klasse.

Der Konstruktor gibt immer eine Referenz auf die Klasse zurück, und da das vorliegende Beispiel diesen Wert nicht zuweist und somit nicht weiter verwertet, spielt sich offensichtlich der gesamte Programmablauf innerhalb des Konstruktors ab. Das ist bei kleineren Projekten durchaus üblich. Sollten Programme in Zukunft umfangreicher und die Klasse nicht nur in dem Programm selbst, sondern auch von anderen Anwendungen genutzt werden, wären eine Referenz zur frisch erzeugten Klasse zu speichern und im weiteren Verlauf unterschiedliche Methoden der Anwendungsklasse über diese Referenz aufzurufen.

Der Artikel beschreibt nun den Programmablauf detailliert. Zuerst ĂĽbergibt man dem Konstruktor das String-Array args[] mit dem Inhalt der Kommandozeile. Wurden keine Parameter ĂĽbergeben, gibt das Programm eine usage-Meldung aus und es wird beendet.

      public JType(String[] args) throws Exception {
if (args.length < 1) usage();
...
}

Hinzuzugefügt sei noch, dass die Array-Klasse die hier abgefragte Integer-Variable length enthält und dass diese von außen zugänglich ist (public).

Als Nächstes definiert man einige Konstanten, die für den weiteren Programmablauf und dem Erreichen des Ziels, auf allen Plattformen lauffähig zu sein, notwendig sind. Dazu zählen der Pfad-Separator, also dem ";" unter Windows beziehungsweise dem ":" unter Unix, der innerhalb der PATH-Variablen die Pfade voneinander trennt, und dann der File-Separator, der unter Windows "\" und unter Unix "/" bedeutet. Diese Werte stellt die Java-Bibliothek innerhalb der System-Klasse bereit. In ihr gibt es die statische Methode getProperty(), die als Argument einen String, der die gewünschte Systemeinstellung (Property) umschreibt. Zurückgegeben wird jeweils ein String, den der Entwickler zur späteren Verwendung speichern muss.

Dann wird die Umgebungsvariable PATH mit der statischen Methode (System.getenv("PATH")) abgefragt und gespeichert. Diese darf nicht leer sein. Also ist zu prüfen, ob das Programm einen null-Wert oder einen leeren String zurückgibt (ist nicht dasselbe). In dem Fall wird eine Fehlerbedingung (eine Exception mit der entsprechenden Meldung erzeugt, und die Programmausführung erfolgt im nächsten "catch"-Block, also im Beispiel im unteren "main"-Teil.

Als letzten Schritt vor der intensiven ĂśberprĂĽfung der Kommandozeile zerlegt man die PATH-Variable in die einzelnen Pfadkomponenten. Das erledigt die String-Methode split(), die als Argument den Separator verlangt. Die gibt ein String-Array mit den einzelnen Komponenten zurĂĽck. Hier jetzt die Zusammenfassung:

      public JType(String[] args) throws Exception {
if (args.length < 1) usage();
String sPathSep=System.getProperty("path.separator");
String sFileSep=System.getProperty("file.separator");
String sPathName=System.getenv("PATH");
if (sPathName==null || sPathName.isEmpty())
throw new Exception ("FATAL: Path variable is empty!");
sPathList=sPathName.split(sPathSep);
...
}

Darauf geht es um die Untersuchung der Kommandozeile, und zwar String für String. Am elegantesten passiert das innerhalb einer for-Schleife. Im Unterschied zu C, wo das Format der for-Bedingung fest vorgegeben ist (for(StartCondition; EndCondition;after each element)), ist bei Java für List- und Array-Objekte eine iterative Variante möglich:

      public JType(String[] args) throws Exception {
...
for (String s: args) {
if (s.startsWith("-") && s.length() > 1) {
for (int i=1; i<s.length(); i++) {
switch(s.toUpperCase().charAt(i)) {
case 'A': bCheckAll=true; break;
case 'L': bLong=true; break;
case 'V': bVerbose=true; break;
}
}
continue;
}
...
}
}

Das Programm JType soll drei Steuerungsoptionen ermöglichen. Die beginnen mit dem Schalter "-", den die oben gezeigte Schleife abgefragt hat. Zahlreiche Methoden der String-Klasse kommen hierbei zum Einsatz und verdeutlichen, wie einfach in Java das String-Handling im Gegensatz zu C ist – das de facto ja gar keinen String im eigentlichen Sinne kennt.

Die Tabelle 2 zeigt einige nĂĽtzliche String-Methoden, die in vielen Programmen immer wieder zum Einsatz kommen.

Name RĂĽckgabewert Argumente Beispiel
charAt Zeichen im String an Position (int) Index im String (int) String s="Text";
int ch=s.charAt(0);
System.out.printf("Zeichen[0]=%c\n",ch);
endsWith Endet der String mit dem Argument?
boolean (true, false)
String mit Endekennung String s="Text.bla";
If (s.endsWith("bla"))
System.out.println(s+" ends with \"bla\"!");
equals Stimmt der String mit dem Argument ĂĽberein?
boolean (true, false)
String, nach dem verglichen werden soll String s="Text";
If (s.equals("Text")) System.out.printf("%s is a Text!\n", s);
equalsIgnoreCase Stimmt der String mit dem Argument (unabh. von GroĂź-/Kleinschr.) ĂĽberein?
boolean (true, false)
String, nach dem verglichen werden soll String s="Text";
If (s.equalsIgnoreCase("text")) System.out.printf("%s is a Text!\n", s);
format Formatiert einen String (vgl. sprintf())
Statisch: String
Format String, 1-n Elemente String s="Text", st=String.format("Der String \"%s\"s enthält %d Zeichen", s, s.length());
System.out.prinln(st);
getBytes Gibt ein byte[] Array zurĂĽck, das den String darstellt. Kein FileOutputStream fos=new FileOutputStream("stest.out");
String s="Text";
byte[] b=s.getBytes();
fos.write(b);
fos.close();
indexOf Gibt die Position des ang. Zeichens oder Strings wieder
(int)
Entweder Zeichen (int) oder String String s="Text";
int pos=s.indexOf('x');
System.out.printf("Das Zeichen 'x' steht an %d. Stelle\n",pos);
split Splittet einen String mit ang. Separator und gibt ein String Array zurĂĽck (String[]) Separator String String s="Text1;Text2";
String[] sl=s.split(";");
for (String st: sl) {
System.out.println(st);
}
startsWith Beginnt der String mit dem Argument?
boolean (true, false)
String mit Startkennung String s="Text.bla";
if (s.startsWith("Text”))
System.out.println(s+" starts with \"Text\"!");
substring Liefert einen Teilstring zurĂĽck (String) Beginnposition (int), oder Beginn- und Endeposition (int,int) String s="Text.bla";
System.out.println(s.substring(5));
…
bla
toLowerCase Liefert den String in Kleinbuchstaben zurĂĽck (String) kein String s="Text.bla";
System.out.println(s.toLowerCase());
…
text.bla
toUpperCase Liefert den String in GroĂźbuchstaben zurĂĽck (String) kein String s="Text.bla";
System.out.println(s.toUpperCase());
…
TEXT.BLA
trim Liefert den String zurĂĽck, wobei am Anfang und am Ende Leerzeichen und Tabs abgeschnitten sind (String) kein String s=" Text.bla ";
System.out.printf("Trimmed size(s)=%d\n", s.trim().length());
…
Trimmed size(s)=8

Das – und vieles mehr – lässt sich übrigens innerhalb der Java-Dokumentation online abrufen.