Atarium: Fantastische Reise in die Tiefen von Kommandoshells

Im TOS, dem Betriebssystem des Atari ST und TT, schlagen bekanntlich zwei Herzen. Da wäre das GEMDOS, das sich um Prozeß- und Dateiverwaltung kümmert. Und es gibt GEM.

Das muß nicht so sein. Beim Betriebssystem des Macintosh beispielsweise ist die direkte Ausgabe von Text ohne Benutzung von Windows oder Dialogboxen praktisch unmöglich. Seine »Zwiespältigkeit« hat TOS mit vielen anderen Betriebssystemen gemeinsam. Man denke nur an AMIGADOS/Intuition oder Unix in Zusammenhang mit einem X Window-System (übrigens: es heißt das »X Window-System«, nicht etwa »X-Windows« - der eigentliche Name ist wirklich nur »X«!!!), wie es auch auf dem TT unter der Unix-Variante ATX zum Einsatz kommen wird.

Die Vorteile zeichenorientierter Programme liegen auf der Hand: Sie lassen sich oft spielend zwischen verschiedenen Betriebssystemen übertragen (gerade dann, wenn sie in C unter Benutzung der Standard-Bibliotheksfunktionen geschrieben sind), sind meist sehr flexibel und von Kommandoshells leicht zu nutzen. Dies soll natürlich keine Aufforderung sein, künftig keine GEM-Programme zu schreiben. In vielen Fällen kann es aber auch sinnvoll sein, GEM-Programme alternativ auch über Kommandozeilenparameter starten zu können. Der Shareware-Desktop »Gemini« beweist, daß sich eine grafische Benutzerführung und eine Kommandoshell harmonisch miteinander kombinieren lassen.

Zunächst ein paar Begriffserklärungen zu diesem Thema. Beim Start eines Programms wird eine Kommandozeile übergeben. Vom Desktop her kennt man dies natürlich von den TTP-Programmen (»TOS takes Parameters«), bei deren Start man eine beliebige Textzeile übergeben kann. Auch wenn man über »Anwendung anmelden« ein Programm für bestimmte Namenserweiterungen avisiert und startet, wird dem Programm eine Kommandozeile (nämlich mit den entsprechenden Dateinamen) mit auf den Weg gegeben. In der Kommandozeile finden üblicherweise Dateinamen und Schlüsselwörter für bestimmte Optionen ihren Platz. Beim Komprimierungsprogramm ARC übergibt man z.B. »X TEST.ARC«, um aus der Archivdatei TEST.ARC alle Dateien zu entpacken (»eXtract«).

Für den ST gibt es mittlerweile eine Reihe von Kommandoshells, die sich überwiegend an bekannten und bewährten Unix-Shells orientieren. Erwähnt seien nur »Guläm« (Public Domain), »Craft«, »Master« oder die »Mupfel« im Shareware-Desktop »Gemini« (siehe [1]). Bei diesen Shells ist es üblich, daß die Kommandozeile erst nach einigen Umwandlungen an das zu startende Programm übergeben werden. Dazu gehören:

Langer Rede, kurzer Sinn: Die meisten Utilities für Kommandoshells gehen davon aus, daß die Shell bereits »vorgekaute« Dateinamen übergibt. In den seltenen Ausnahmefällen muß man die Dateimaske in zusätzliche Anführungsstriche setzen, damit sie unverändert an das gestartete Programm weitergeleitet wird. Dies nennt man dann »Quoting«. Es ist bekannt, daß Unix und C in enger Verbindung stehen. Daher verwundert es kaum, daß es in C besonders einfach ist, an die Werte aus der Kommandozeile zu kommen.

Abb. 1: Die Hauptfunktion main() mit zwei Variablen im C-Programm

int main (int argc, char * *argc);

argc: Anzahl der Parameter eingeschlossen dem Programmnamen in argv[0]

argv[0]: Name des Programms

ab argv[1]: die einzelnen Parameter aus der Kommandozeile

Die Hauptfunktion »main()« erhält zwei Parameter (siehe Bild): zum einen die Anzahl der übergebenen Argumente in der Integer-Variablen »argc« (»argument count«). Zum anderen findet man in »argv« (»argument vector« einen Zeiger auf ein String-Array, in dem man die einzelnen Wörter aus der Kommandozeile findet. In diesem Zusammenhang treten häufig ein paar typische Schwierigkeiten auf:

Soviel zur Kommandozeile. Vielleicht das wichtigste Merkmal eines »reinrassigen« Tools in Kommandoshells ist die Beschränkung auf die Zeichenkanäle »stdin« und »stdout«. In der C-Standardbibliothek bezeichnet man damit die »Dateien«, die (mehr oder weniger) fest mit dem Bildschirm für Ein- und Ausgabe verbunden sind. Auf GEMDOS-Ebene handelt es sich um die Standardkanäle 0 (stdin) und 1 (stdout), die man mit allen GEMDOS-Funktionen direkt benutzen kann, ohne sie erst öffnen zu müssen. Auch GEMDOS-Standardfunktionen »Cconout()« oder »Cconrs()« benutzen immer Kanal 0 und 1.

Das Schöne an den GEMDOS-Standardkanälen ist, daß man sie umlenken kann. Ein Beispiel: Wenn das Archivierungsprogramm ARC ein Inhaltsverzeichnis ausgibt, benutzt es den GEMDOSKanal »stdout« (Kanal 1). In den meisten Shells kann man nun die Ausgabe von »stdout« umlenken. Das gestaltet sich dann beispielsweise so:

ARC V TEST.ARC >FILELIST

Die Shell sieht dann das Kommando »>FILELIST« und wertet dies als Aufforderung, auf »stdout« vorgenommene Ausgaben in die Datei »FILELIST« zu schreiben. Also wird diese Datei angelegt, und der Kanal »stdout« mittels der GEMDOS-Funktion »Fforce()« umgeleitet. Dabei sind auch Variationen möglich:

Was für »stdout« gilt, stimmt auch für »stdin«. Der Komprimierer ZOO kennt z.B. einen Modus, bei dem die Namen der zu komprimierenden Dateien von »stdin« eingelesen werden. Man könnte damit beispielsweise mit einem Editor eine Textdatei FILES.TXT mit den Namen der zu komprimierenden Dateien anlegen und dann mittels

ZOO a| ZOOFILE.ZOO < FILESTXT

von ZOO verarbeiten lassen. Hier wird also ganz analog das Kleiner-Zeichen als Symbol für die Umlenkung benutzt.

Unter Unix gibt es nun wiederum eine Reihe von Standardprogrammen, die einfach nur Zeichen von »stdin« lesen, irgendwie verarbeiten, und dann auf »stdout« ausgeben. So ein Programm nennt man dann »Filter«. Viele Programme machen dies davon abhängig, ob man sie mit oder ohne Parameter aufruft. Besonders praktisch sind solche Programme, wenn man eine Shell besitzt, die sog. Pipes erlaubt. Durch eine Pipe kann man die Standardausgabe eines Programms mit der Standardeingabe eines zweiten Programms verbinden. Bei einem Multitasking-Betriebssystem laufen die einzelnen Programme dann sogar tatsächlich gleichzeitig ab. Und wieder ein Beispiel:

ls * zoo a| zoofile

Der senkrechte Balken ist das sog. Pipe-Symbol, wie es jede Unix- und Unix-artige Shell versteht. Die Ausgabe des ersten Kommandos (ls *) wird direkt als Eingabe für ZOO benutzt. Echte Pipes sind unter TOS natürlich nicht möglich. Mit Hilfe der Ein-/Ausgabeumlenkung über eine temporäre Datei kann man allerdings ein ähnliches Ergebnis erzielen:

ls * >TEMPFILE
zoo a| <TEMPFILE

Pipes können selbstverständlich auch mehr als zwei Stufen haben. Das Schöne an diesen Mechanismen: Solange die Programme nur sauber über »stdin« und »stdout« operieren, brauchen sie selbst nichts darüber zu wissen: Die Hauptarbeit liegt bei der Shell. Ganz problemlos ist dies allerdings nicht; unterstellen wir mal den folgenden Fall:

ARC V ARCHIV.ARC > output

Mal angenommen, die Archivdatei ARCHIV.ARC sei gar nicht vorhanden: Wie soll dann ARC dem Benutzer mitteilen, daß ein Fehler vorliegt? Die einfache Lösung unter Unix: Zusätzlich zum Standardausgabekanal »stdout« gibt es den Standard-Fehler-Kanal »stderr«, auf dem man Fehler melden und Warnungen ausgeben kann. Die wichtigsten Eigenschaften von »stderr«:

Ein besonderer Leckerbissen: Wie eben schon angedeutet, kann man von »stderr« auch Zeichen lesen. Wozu man das nun wieder braucht? Das Standardtool »more« liest beispielsweise Zeilen von »stdin« und bietet diese seitenweise (zum Blättern) an. Nur, woher soll »more« seine Tastendrücke bekommen, wenn »stdin« schon mit einer Datei verbunden ist? Die einzige Möglichkeit ist, auch das Lesen von »stderr« zu erlauben!

Kommen wir zu dem unvermeidlichen (?) Haken an der Sache: Leider hat Atari im GEMDOS keinen Standardfehlerkanal vorgesehen. Die Hersteller der C-Compiler haben deshalb verschiedene Methoden benutzt, um einen Ersatz anzubieten. Da gäbe es erstmal die Compiler, die »stderr« und »stdout« einfach auf denselben Kanal lenken, womit natürlich fast alle Vorteile zunichte gemacht werden. Nicht zur Übernahme empfohlen.

Eine zweite Möglichkeit ist, »stderr« fest mit dem Bildschirm zu verbinden (über GEMDOS-Kanal -1). Damit sind zwar »stdout« und »stderr« voneinander getrennt, aber »stderr« kann dadurch nicht mehr umgelenkt werden. Ferner gehen viele Unix-Programme, die man gerne portieren möchte (z.B. »compress«) davon aus, daß »stderr« Kanal 2 ist. Diese Lösung wird meines Wissens von der Turbo-C-Library benutzt. Elegant, aber nicht ganz problemlos!

Die offensichtliche Lösung ist, für »stderr« wie unter Unix Standardkanal 2 zu benutzen. Kanal 2 wird von GEMDOS normalerweise für die serielle Schnittstelle benutzt. Allerdings hat Atari bereits 1986 [3] darauf hingewiesen, daß man besser direkt über das BIOS auf die serielle Schnittstelle zugreifen sollte.

Alle zum Mark-Williams-C-Compiler gehörigen Tools und auch die von diesem Compiler erzeugten Programme nutzen für »stderr« Kanal 2. Der Nachteil: Normalerweise ist Kanal 2 eben mit der seriellen Schnittstelle verbunden - nur wenn man in seiner Shell Kanal 2 auf den Bildschirm umlenkt, bekommt man eventuell Fehlermeldungen zu Gesicht (davon sind auch die meisten GNU-Programme betroffen). Nichtsdestotrotz gibt es keine bessere Alternative. Atari-Programmierer Allan Pratt machte folgenden Vorschlag: beim Start sollte jedes Programm testen, ob Kanal 2 bereits auf eine Datei umgelenkt ist. Dazu kann man die Funktion »isatty()« aus den TOS-Release-Notes (Bild 2) benutzen. Wenn dem nicht so ist, sollte man mittels »Fforce (2, -1)« die Ausgabe auf den Bildschirm umlenken. Damit sind schon ein paar Probleme gelöst. Startet man ein solches Programm vom Desktop oder von einer »unwissenden« Shell, dann wird die Ausgabe von »stderr« auf den Bildschirm umgelenkt. Hat man eine intelligente Shell und möchte man »stderr« in eine Datei umlenken, geht das auch. Ein kleiner Haken bleibt jedoch. Ein Kommando wie

ARC X TESTFILE 2 > PRN:

(also: gib die Fehlermeldungen auf dem Drucker aus) wird nicht richtig verstanden. Aber auch das läßt sich leicht aus der Welt schaffen. In jeder »guten« Shell kann man Environmentvariablen setzen. Wenn der Startupcode das Vorhandensein der Environmentvariablen »STDERR« als Aufforderung wertet, Kanal 2 einfach dort zu lassen, wo er ist, sind alle Probleme beseitigt. Das Schöne an dieser Methode im Überblick:

Nun noch ein Hinweis zu älteren TOS-Versionen: Vor TOS 1.4 funktionierte die Ein-/Ausgabeumlenkung nur teilweise: So konnten manche zeichenorientierte GEMDOS-Funktionen gar nicht umgelenkt werden, die Umlenkung auf den Drucker war auch nicht fehlerfrei. Wieder mal ein Grund, sich ein aktuelles TOS zu besorgen...

Fazit

Für viele Tools ist es sinnvoll, sich an die oben beschriebenen Methoden zu halten. Es ist nicht einzusehen, warum ein Programmpacker die Maus ein- und ausschalten muß und nicht auf einem an einer seriellen Schnittstelle angeschlossenen Terminal laufen soll (Benutzung in Mailboxen!!!).

(mb)

Literatur:

[1] Julian F. Reschke, »Bericht: Gemini-Shell«, ST-Magazin 2/1990, Seite 22

[2] Julian F. Reschke, »Zum 25. Mal: unser Atarium«, ST-Magazin 1/1990, Seite 56

[3] Atari Corporation, »Atari GEMDOS Reference Manual«, 4.4.1986

Abb. 2: Wie man feststellt, ob ein Kanal mit einer Datei verbunden ist

int isatty (int handle)
{
 long oldoffset;
 long rc;
 oldoffset = Fseek (OL, handle, 1); /* Seek versuchen */
 rc = Fseek (1L, handle, 0);
 Fseek (oldoffset, handle, 0);
 return (rc ! = 1L ); /* ging's oder nicht? */
}

Quelle: TOS 1.4 Release Notes, Atari Corporation


Abb. 3: Drei Tips für die gezielte Anwendung von »stderr« im Startupcode

1. stderr ist GEMDOS -Kanal 2

2. Falls die Environmentvariable »STDERR« existiert, dann lasse man Kanal 2 so, wie er ist.

3. Ansonsten: falls Kanal 2 nicht mit einer Datei verbunden ist (isatty() benutzen!), dann lenke man ihn mittels Fforce (2, -1) auf den Bildschirm um.


Julian F. Reschke
Aus: ST-Magazin 08 / 1990, Seite 53

Links

Copyright-Bestimmungen: siehe Über diese Seite