Die Unix-Philosophie
von Christian Weisgerber (naddy@unix-ag.uni-kl.de) nach Mike Gancarz 

Die kontinuierlich steigende Popularität der Unix-Betriebssysteme seit ihren Anfängen und ihre resultierende heutige Mächtigkeit, denen sie ihren ersten Rang als Plattform für Serveranwendungen im Internet verdanken, beruht auf der konsequenten Anwendung einiger weniger fundamentaler Prinzipien. Für den produktiven Einsatz wie auch die weitere Entwicklung von Unix ist es wichtig, daß neue Anwender und Programmierer diese Grundsätze verinnerlichen. 

Einleitung

Dieser Beitrag ist eine Zusammenfassung des Buchs 

Mike Gancarz
The UNIX Philosophy
Digital Press, 1995
ISBN 1-55558-123-4
151 Seiten 

das an dieser Stelle empfohlen sei. 

Eine der grundlegenden Annahmen, welche die Richtung der Unix-Entwicklung gewiesen hat, war die, daß der Benutzer mit dem Computer umgehen kann, daß er weiß was er tut. Als Folge daraus sind, anders als in anderen Umgebungen, Unix-Programme nicht darauf ausgerichtet, den Benutzer daran zu hindern etwas zu tun, sondern ihm zu ermöglichen ihre volle Leistungsfähigkeit auszuschöpfen. 

Die Grundsätze der Unix-Philosophie sind von einer trügerischen Einfachheit. Konsequent angewendet führen diese Konzepte jedoch zu mächtigen Ergebnissen. Im folgenden werden sie in einer Reihe von einprägsamen Leitsätzen zusammengefaßt und diese jeweils erläutert. Die sequentielle Darstellung soll nicht täuschen, diese Prinzipien greifen alle ineinander und machen in ihrer Kombination die Mächtigkeit des Systems aus. 

1. Klein ist schön

Kleine Programme sind leicht zu verstehen. Durch ihre begrenzte Quelltextlänge und Funktionalität bleiben kleine Programme überschaubar, was wiederum zu weniger Fehlern führt. Natürlich kann ein Programm gleich welcher Größe aufgrund seiner besonderen Funktionalität schwer zu verstehen sein, aber dies ist die Ausnahme. Es gibt keine allgemeingültige Regel, wann aus einem kleinen Programm ein großes wird. Warnzeichen sind z.B. wenn der Entwickler bei einer Fehlermeldung selbst nicht mehr weiß, wo sie herkommt, oder wenn man den Quelltext ausdrucken muß, um den Überblick zu wahren. 

Kleine Programme sind leicht zu warten. Da ein kleines Programm im allgemeinen leicht zu verstehen ist, ist es auch leicht zu warten (korrigieren, erweitern, portieren). 

Kleine Programme verschlingen weniger Systemressourcen. Sei es Plattenplatz oder Speicher, kleine Programme sind schonender im Umgang mit begrenzten Betriebsmitteln. Man mag einwenden, daß die Summe mehrerer kleiner Programme mehr Ressourcen benötigt als ein großes mit der gleichen Funktionalität, aber: 

Kleine Programme lassen sich leichter mit anderen Werkzeugen kombinieren. Wie weiter unten noch angesprochen wird, liegt die wahre Mächtigkeit des Unix-Konzepts in der Kombinierbarkeit vieler kleiner Werkzeuge. Kein Autor kann alle Anwendungen, die seine Programme erfahren werden, voraussehen. Große, monolithische Anwendungen sind wenig flexibel. Eine Sammlung kleiner, handlicher Programme ist neuen Anforderungen viel eher gewachsen. 

2. Jedes Programm soll genau eine Sache gut machen

Kleine Programme haben meistens nur eine Funktion. Programme mit nur einer Funktion neigen wiederum dazu klein zu sein, was sich mit dem 1. Leitsatz von oben ergänzt. Ein viel kritisiertes Problem ist der sogenannte »creeping featurism«, der schleichende Einzug von immer mehr Funktionalität, die eigentlich überflüssig ist oder ausgelagert gehört, und die kleine Programme zu großen aufbläht. 

Einige Fragen, die man sich stellen kann, um das Wuchern des schöpferischen Drangs in sinnvolle Bahnen zu lenken: Muß ein Programm interaktiv sein oder reicht es nicht, wenn der Benutzer die Eingaben in eine Datei oder über Kommandozeilenparameter macht? Falls Ein-/Ausgabedaten besonders formatiert werden sollen, gibt es dann nicht schon andere Werkzeuge, die das übernehmen können? Gibt es überhaupt nicht bereits ein ähnliches Programm das man einfach verwenden kann? 

3. Erstelle so schnell wie möglich einen Prototyp

Dieser Leitsatz steht im Widerspruch zur gängigen Lehre, daß der Entwurf eines Programms schon abgeschlossen sein soll, bevor man dann im letzten Schritt die Implementierung vollzieht. »90% des Programmentwurfs sollten vor dem ersten Compilerlauf vollzogen sein«, so oder ähnlich heißt es. Das klingt vernünftig in der Theorie, beruht aber auf der unrealistischen Annahme, daß der Entwurf von vorneherein schon komplett überschaubar ist. 

Die Erstellung eines Prototypen ist ein lehrreicher Vorgang. Man kann frühzeitig erkennen, ob das Projekt in dieser Form überhaupt realisierbar ist, und wie die eigenen Vorstellungen sich in der Praxis bewähren. Es ist besser eine fehlerhafte Annahme frühzeitig zu erkennen als kurz vor Fertigstellungsfrist. 

Die frühe Anfertigung eines Prototypen verringert Risiken. Die Erfahrung zeigt, daß Programmspezifikationen in der Zeit, die ein Projekt zur Fertigstellung braucht, nicht stabil sind, und daß Anwender und Auftraggeber oft Spezifikationen abgeben, die vom Programmierer anders verstanden werden oder schlicht falsch sind. Ein früher Prototyp hilft solche Fehler zu erkennen, bevor Zeit und Arbeit vergeudet sind. 

4. Portabilität geht über Effizienz

Eigentlich ist es eine triviale Erkenntnis: Der mächtigste Computer ist nicht der mit der schnellsten CPU und dem meisten Speicher, sondern der, der am meisten benutzt wird. Ein Laptop, den man den ganzen Tag bei sich hat, kann eine viel größere Produktivität haben, als der vielfach leistungsfähiger Tischrechner, der unbenutzt im Büro steht. 

Aber auch in der umgekehrten Richtung ist Portabilität Trumpf. Es hat keinen Sinn, zuviel Zeit damit zu vergeuden, ein Programm durch systemspezifische Optimierungen schneller zu machen. Nächstes Jahr, bei den heutigen Produktzyklen vielleicht schon nächstes Quartal, wird die Hardware schneller sein. Die effizienteste Programmierung ist selten die portabelste. Niemand wird sich mehr dafür interessieren, daß eine Applikation früher auf einer bestimmten Plattform viel schneller als die anderen war, wenn man sie auf der neuen Plattform, wo alle schneller sind, erst gar nicht einsetzen kann. Portable Software verringert auch den Bedarf für Benutzerschulung. 

Gute Programme sterben nie, sie werden auf neue Plattformen portiert. 

5. Speichere numerische Daten in flachen ASCII-Dateien

Oft übersehen, aber genauso wichtig wie die Portabilität von Programmen ist die Portabilität der Daten. Ein wesentlicher Beitrag dazu ist, numerische Daten nicht in einem Binärformat zu speichern, sondern in »flachem« ASCII-Text. 

ASCII-Text, mit allen seinen Schwächen, ist das verbreitetste Austauschformat. Er unterliegt keinen Problemen mit der Byte-Reihenfolge oder verschiedenen Fließkommaformaten, die den Austausch von Binärdaten immer wieder komplizieren. Er ist leicht zu lesen und mit normalen Editoren und unter Einsatz der Unix-Text-Werkzeuge wie cut, diff, grep, sed, sort, wc usw. leicht zu bearbeiten. Bessere Portabilität überwiegt gegenüber dem Geschwindigkeitsverlust durch die Wandlung bei der Ein-/Ausgabe, der in der Praxis im Vergleich zur eigentlichen Verarbeitungszeit ohnehin meist vernachläßigbar ist. 

6. Nutze die Hebelwirkung von Software zu deinem Vorteil

Ein vielbemühtes Bild ist das des Hebels, bei dem man mit dem Einsatz geringer Kraft große Lasten bewegen kann, und man spricht vor allem im anglo-amerikanischen Raum auch gerne im übertragenen Sinn von Hebelwirkung (engl. »leverage«). 

Gute Programmierer schreiben guten Code, wirklich große Programmierer borgen guten Code. Es bringt keinen Fortschritt, das Rad immer wieder neu zu erfinden. Mehrwert heißt das kommende, wenn nicht schon aktuelle Schlagwort. Es gilt den eigenen Stolz zu bezwingen und das sogenannte NIH-Syndrom zu vermeiden (engl. »Not Invented Here«), und auch Dinge, die nicht selbst oder nicht im eigenen Haus erfunden worden sind, einzusetzen. Umgekehrt ist es wichtig, anderen Leuten zu erlauben seinen eigenen Code zu verwenden, damit diese mit ihrer Arbeit darauf aufbauen können. 

Automatisiere alles. Ein mächtiges Mittel die Hebelwirkung der Software zum eigenen Vorteil einzusetzen, ist es die Arbeit der Maschine zu übertragen. Es ist Zeitverschwendung etwas manuell zu machen, wenn der Computer es auch tun kann. Immer wiederkehrende, monotone Arbeiten sind typische Kandidaten für eine Automatisierung. Genau hier schlägt die Batchfähigkeit von Programmen zu und das Konzept der vielen kleinen unabhängigen Werkzeuge und der Filter, das bei anderen Leitsätzen angesprochen wird. Insbesondere Umsteiger von MS-Windows scheinen eine geradezu masochistische Hingabe zu haben, sich wiederholende Arbeiten immer wieder manuell auszuführen. Möglicherweise hängt das mit der von ihnen gewöhnten Benutzeroberflächenphilosophie zusammen, vgl. Leitsatz 8, die eine Ausnutzung der Hebelkraft oft unmöglich macht. 

7. Shell-Skripte vergrößern Hebelwirkung und Portabilität

Viele gestandene Unix-C-Programmierer haben eine abfällige Haltung gegenüber Shell-Skripten. Das erinnere so sehr an BASIC, sei eben nur ein Spielzeug für Leute, die nicht richtig programmieren könnten. Tatsächlich gilt es, das richtige Werkzeug für die richtige Aufgabe auszuwählen. 

Shell-Skripte können eine ungeheure Hebelkraft verleihen. Der Übergang von komplexen Befehlskombinationen auf der Kommandozeilen zu Skripten ist fließend. Man kann sich überlegen, wieviele tausend Zeilen C-Quelltext z.B. hinter ein paar Aufrufen der typischen Unix-Textwerkzeuge stehen, auf die man so leicht zurückgreift. Shell-Skripte erlauben die eigene Zeit effizienter zu nutzen, indem man bereits vorhandene Funktionalität nutzt, und der Zyklus N-E-C-T (nachdenken, editieren, compilieren, testen) auf N-E-T schrumpft. Shell-Skripte können portabler als C sein. Sie erlauben oft die schnelle Erstellung von Prototypen, vgl. Leitsatz 3. Oft drängt es einen Shell-Skripte in C neuzuschreiben, aber gibt es wirklich handfeste Gründe wie eine zu geringe Arbeitsgeschwindigkeit, oder ist es nur eitler Stolz? 

8. Vermeide Benutzeroberflächen, die den Benutzer gefangen halten

Der Einfachheit halber wird im folgenden für eine solche Oberfläche die englische Abkürzung »CUI« (Captive User Interface) verwendet. Sie ist dadurch charakterisiert, daß eine Applikation eine Interaktion mit dem Benutzer außerhalb des höchsten Befehlsinterpreters erzwingt. Ist die Anwendung einmal gestartet, gehen alle Eingaben des Benutzers an sie und nicht an den Befehlsinterpreter, bis das Programm wieder beendet wird. 

CUIs Befehlsparser sind oft groß und häßlich zu schreiben. Der Aufwand für das Gestalten einer kompletten Curses-Oberfläche mit allen Terminaloperationen oder gar für eine Oberfläche unter X11 sind schnell so aufwendig, daß sie die eigentliche Funktion des Programms in den Schatten stellen. 

CUIs neigen zu "groß ist schön" im Wiederspruch zu Leitsatz 1. Da der Benutzer in der Oberfläche gefangen ist, hat er keinen direkten Zugriff auf andere Systemfunktionen, weswegen zunehmend Druck entsteht, diese innerhalb der Applikation zu duplizieren. 

Programme mit CUI sind schwer mit anderen Programmen zu kombinieren, weil sie auf der Annahme basieren, der Benutzer sei ein Mensch. Die Stärke von Unix liegt aber darin, wie Programme miteinander arbeiten. Programme mit CUI lassen sich gar nicht oder nur sehr schwer in Skripte einbinden, Abläufe nicht automatisieren, sie ziehen keinen Vorteil aus der Software-Hebelwirkung. Da sie nicht mit anderen Werkzeugen kombiniert werden können, müssen deren Funktionen mit eingebaut werden. In einem Teufelskreis gefangen wächst das Programm zu einem immer größeren und schwerfälligeren Monolith. 

CUIs skalieren nicht. Ein Programm das z.B. der Reihe nach alle notwendigen Angaben abfragt, die notwendig sind um einen neuen Benutzerzugang auf einem System anzulegen, oder ein entsprechendes Menü anbietet, mag auf den ersten Blick benutzerfreundlich aussehen, aber wenn damit tausend Benutzer einzutragen sind, dann wird das nichts. Es sei an dieser Stelle angemerkt, gerade weil sich der LinuxTag auch an Umsteiger von MS-Windows wendet, daß die Arbeit in der PC-Welt, von den Tagen von MS-DOS bis zum heutigen MS-Windows (NT), durch CUIs bestimmt ist, mit weitreichenden Konsequenzen. 

9. Gestalte jedes Programm als Filter

Programme schaffen keine Daten, Menschen tun das. Computer verarbeiten nur Daten von einer Form zu einer anderen. Jedes seit der Urzeit des Computers geschriebene Programm ist auf seine Art ein Filter. In der Unix-Umgebung wendet man im engeren Sinn die Bezeichnung »Filter« auf solche Programme an, die ihre Ein-/Ausgaben per stdio abwickeln, so daß sie sich zu beliebig langen Ketten zusammenschalten lassen, wo die Ausgabe des einen Programms die Eingabe des nächsten ist. 

Die Regeln für ein Filter sind denkbar einfach: 

  1. Benutze stdin für die Dateneingabe. 
  2. Benutze stdout für die Datenausgabe. 
  3. Benutze stderr für Informationen nebenläufig zur Ausgabe, insbesondere Fehlermeldungen. 
Niemand kann im voraus wissen, wie sein Programm eingesetzt werden wird. Filter sind flexibel, da ihre Ein-/Ausgabe nicht festgelegt ist. Sie können aus einer Datei lesen, von einem Terminal, oder insbesondere von einem anderen Programm. Sie können in eine Datei schreiben, auf ein Terminal, an einen Drucker, oder wieder an ein anderes Program. Sie sind klein und kombinierbar. 

Die Wichtigkeit

Kaum einer der vorgestellten neun Leitsätze der Unix-Philsophie kann für sich alleine stehen, aber zusammen ergeben sie ein mächtiges Ganzes. Die Kenntnis dieser Prinzipien hilft dem Anwender die wahre Leistungsfähigkeit des Systems auszuschöpfen. Der Programmierer muß sie kennen, damit sich seine Programme nahtlos in das System einfügen. Für Umsteiger von anderen Betriebssystemen ist das Verständnis dieser Konzepte der Schlüssel zum erfolgreichen Umgang mit Unix. Dies betrifft insbesondere Linux, dessen erklärtes Ziel es ist, den Desktopbereich von Microsoft zu übernehmen. Für die erfolgreiche Zukunft aller Unix-Varianten ist es wichtig, daß nicht falsch verstandene Anpassung an die Konzepte anderer Betriebssystems die Basis der eigenen Fähigkeiten untergräbt.