Kritik der Einfuehrungsbeispiele des Buches =========================================== Programmieren mit awk ===================== von Jürgen Peters (2., verbesserte (!) Auflage, Oldenbourg, 1993) ================================================================= 2019-10/2019-12, markus schnalke Nachfolgend gehe ich die zwoelf Einfuehrungsbeispiele (Abschnitt 1.1) des Buches durch. Sie sollen, laut Autor, einen Eindruck von awk als Werkzeug fuer den taeglichen Umgang mit Datenmanipulationen vermitteln. Die eingerueckten Beschreibungen sind dem Buch entnommen. Sie bilden die Grundlage meiner Analyse. Mir geht es darum, die Qualitaet der Einfuehrungsbeispiele, sowohl als awk-Programme an sich als auch als didaktische Beispiele, zu pruefen. Neben der Kritik der Buchbeispiele liefere ich auch konstruktive Verbesserungsvorschlaege, diese in erster Linie im Hinblick auf die Qualitaet der awk-Programme aus Programmierersicht und nur zweitrangig aus didaktischer Sicht. 1) Fuehrende Leerzeichen entfernen ---------------------------------- Wenn Texte, die mit einem Standardeditor erfasst wurden, in einem Textverarbeitungprogramm verarbeitet werden sollen, tritt nicht selten da Problem auf, dass zunaechst alle Leerzeichen am Beginn jeder Zeile entfernt werden muessen. Das folgende Kommando loescht fuehrende Leerzeichen aus jeder Zeile der Datei `textmit' und schreibt die so modifizierten Daten in die Datei `textohne'. Die Datei `textmit' bleibt unveraendert erhalten: awk '{gsub(/^ /,"");print $0}' textmit > textohne Tatsaechlich loescht das Programm nicht fuehrende Leerzeichen, sondern nur genau *ein* fuehrendes Leerzeichen. Der Code entspricht also nicht der Beschreibung. Eine korrekte Implementierung waere: awk '{gsub(/^ +/,"");print $0}' textmit > textohne Diese ist aber weiterhin etwas seltsam, da `gsub()' verwendet wird obwohl durch den Zeilenanfangsanker (`^') maximal eine einzige Ersetzung moeglich ist. Das aendert zwar nichts an der Funktion des Programms, aber mit `sub()' ist es semantisch sinnhafter: awk '{sub(/^ +/,"");print $0}' textmit > textohne Weiterhin verwundert es, dass im ersten Beispiel `$0' verwendet wird, statt `print' ohne Argument aufzurufen, was semantisch identisch ist, aber fuer Anfaenger vermutlich einfacher verstaendlich: awk '{sub(/^ +/,"");print}' textmit > textohne Dadurch wuerde man dadurch genug Platz auf der Zeile gewinnen, dass man sich nach dem Strichpunkt einen Leerraum goennen kann: awk '{sub(/^ +/,""); print}' textmit > textohne Ebenfalls koennte man die Ausgabeumleitung leerraumfrei schreiben, um ein Zeichen zu sparen, wenn dies noetig waere: awk '{sub(/^ +/,""); print}' textmit >textohne (Ich finde diese Variante persoenlich auch leichter verstaendlich als die mit Leerraum. Zudem war im fruehen Unix dies die einzig moegliche Variante.) Btw: Bei awk-Profis wird man hin und wieder auch diese Variante antreffen: awk '{sub(/^ +/,"")} 1' textmit >textohne ... sie erfordert aber gutes Verstaendnis von Bedingungen und Aktionen, weswegen sie (auch wenn sie ein awk-Idiom darstellt), in Einfuehrungsbeispielen verfrueht ist. Auf einer grundsaetzlicheren Ebene erscheint mir dieses Beispiel jedoch deshalb schlecht gewaehlt, weil diese Funktion, sicherlich von jedem awk-Programmierer eher mit sed umgesetzt werden wuerde: sed 's/^ *//' textmit >textohne Man kann es natuerlich auch mit awk machen, aber wer wuerde dafuer nicht lieber sed hernehmen? Erste Beispiele sollten moeglichst in der Praxis typische Verwendungen der Sprache darstellen, nicht nur theoretisch moegliche. Das, meiner Meinung nach, erste Beispiel sollte folgendes sein: awk '{print $3}' (Abgrenzung zu cut ueber beliebig viel Whitespace als Trenner.) Das zweite Beispiel sollte folgendes sein: awk '{print $2, $1}' Dieses laesst sich mit keiner Sprache so einfach umsetzen wie mit awk. Anschliessend kaeme das Finden bestimmter Records ueber numerische Vergleiche, wie z.B.: awk '$4 > 120' Und danach das Aufsummieren: awk '{sum += $4} END{print sum}' Mit Beispielen dieser Art sieht es hier aber mau aus ... leider. Dennoch ist das zweite Beispiel deutlich besser als das erste ... 2) Felder in Datensaetzen lesen ------------------------------- In einer Datei `adr' sind Adressen und persoenliche Daten von Verwandten, Freunden, Bekannten, Kollegen usw. gespeichert. Jeder Datensatz enthaelt sieben Eintraege: Nachname, Vorname, Strasse, Wohnort, Telefonnummer, Beruf, Geburtstag, also z.B.: Knop,Karl,Illstr.7,Berlin,030/123456,Wirt,5.5.55 Die Eintraege sind durch Kommas voneinander getrennt. Aus der Datei soll eine nach dem Vornamen alphabetisch sortierte Telefonliste erstellt werden. Das folgende Programm erledigt die Aufgabe in Verbindung mit dem sort-Kommando: awk -F, '{print $2, $1, $5}' adr | sort Dies erscheint mir ein sinnvolles Programm zu sein, das sich so in der Praxis wiederfinden wird. Hier sieht man das Aendern der Reihenfolge der Felder und die Auswahl von Feldern. Ebenso wird die Kombination mit anderen Programmen (`sort'), die fuer awk typisch ist, demonstriert. -- Ein gutes Beispiel. 3) Felder in Datensaetzen vertrauschen -------------------------------------- Bleiben wir bei der Adressdatei. Nehmen wir an, dass Nachnamen und Vornamen in umgekehrter Reihenfolge stehen sollen. Das folgende awk-Programm erledigt das fuer Sie und schreibt die geaenderten Datensaetze alphabetisch sortiert in die Datei `adr_2': awk '{tmp = $1; $1 = $2; $2 = tmp; print $0}' \ adr | sort > adr_2 Haette man den Code automatisch getestest, dann waere aufgefallen, dass das Programm gar nicht funktioniert! Der Feldtrenner in der Datei -- es ist die gleiche Datei von oben -- ist das Komma, der Befehl hier splittet aber an Whitespace. Es fehlt also ein `-F,', damit der Code funktioniert. (Etwas peinlich fuer die zweite, korrigierte (!) Auflage.) Ich finde, es waere noetig gewesen, hier zu erklaeren, warum man es so umstaendlich macht, mit dem Hilfsvariablen-Swap, statt es direkt so zu schreiben: awk '{print $2, $1, $3, $4, $5, $6, $7}' ... Das waere weniger Code und einfacher lesbar gewesen. Es gibt ja Gruende fuer erstere Umsetzung, aber ein Erklaerungssatz dazu oder eine Abgrenzung oder ein Bezug zur anderen Variante waere gut gewesen. 4) Ausgewaehlte Zeilen loeschen ------------------------------- Sie wollen den Eintrag von Klaus-Dieter aus Ihrer Adressdatei loeschen. Mit dem folgenden Programm legen Sie eine Datei `adr_3' an, in der Klaus-Dieter nicht mehr vorkommt. awk '/Klaus-Dieter/{next} {print}' adr > adr_3 ... kann man schon so machen, aber kein awk-Programmierer wuerde es auf diese Weise tun, denn viel direkter ist: awk '!/Klaus-Dieter/' adr >adr_3 Diese Umsetzung ist semantisch allerdings immer noch so ungenau wie erstere. Was mit ``Klaus-Dieter'' recht zuverlaessig funktionieren mag, bricht bei einem Namen wie ``Wolf'' ploetzlich ein. Nicht nur loescht: awk '!/Wolf/' adr >adr_3 Den Eintrag von ``Wolfgang'', sondern auch alle Eintraege aus ``Wolfsburg'' -- ein Datendesaster! Dabei waere es so einfach korrekt zu implementieren: awk -F, '$2 != "Wolf"' adr >adr_3 (Auch hier musste ich wieder das noetige `-F,' ergaenzen.) Wenn man Beispiele gibt, sollten sie auch solide sein. Btw: Um dem ersten Satz der Beschreibung gerecht zu werden, muesste man noch folgendes an den Befehl anhaengen: && mv adr_3 adr Auf diese Thematik koennte man bei einem der Beispiele durchaus eingehen, da sie bei awk ja doch inherent vorhanden ist. 5) Inhaltsverzeichnis erstellen ------------------------------- Das folgende awk-Programm erstellt ein Inhaltsverzeichnis. Alle Zeichen der Datei `datei', die mit einer Dezimalklassifikation beginnen (z.B. 1, 1.1, 2, 3.2.3), werden in die Datei `datei.inh' geschrieben. Die Auswahlbedingung /^[1-9][1-9]?(\.| )/ (ein regulaerer Ausdruck) trifft alle Zeilen, die mit einer ein- oder zweisstelligen Zahl beginnen, auf die ein Leerzeichen oder ein Punkt folgt (siehe Abschnitt ``Regulaere Ausdruecke'' im Nachschlageteil). awk '/^[1-9][1-9]?(\.| )/' datei > datei.inh Das Programm ist sowohl etwas umstaendlich als auch semantisch falsch. Was bitte ist mit dem Abschnitt ``10.1''? Er wird nicht erkannt werden. Dazu must die Regexp so geaendert werden: awk '/^[1-9][0-9]?(\.| )/' datei >datei.inh Und zur Umstaendlichkeit: Wer nimmt schon Alternationen, wenn man eine Zeichenklasse verwenden kann, die zugleich das Escapen erspart? awk '/^[1-9][0-9]?[. ]/' datei >datei.inh Schoener waere es zudem, fuehrenden Whitespace zu erlauben: awk '/^[ \t]*[1-9][0-9]?[. ]/' datei >datei.inh Wenn wir schon dabei sind, koennen wird gleich noch Zahlen beliebiger Laenge und eine mit Null beginnende Nummerierung (wie sie beispielsweise Donald Knuth verwendet) erlauben: awk '/^[ \t]*[0-9]+[. ]/' datei >datei.inh Dies liesse sich auch noch etwas exakter beschreiben, um nicht nur den ersten Teil der Gliederungsnummer, sondern den ganzen Ausdruck zu matchen: awk '/^[ \t]*[0-9](\.[0-9]+)* /' datei >datei.inh Einen so elaborierten Ausdruck braucht es im ersten Beispiel allerdings vielleicht noch nicht. Der Usecase macht aber grundsaetzlich Sinn fuer awk. Zwar wuerde man in dem konkreten Fall eher egrep dafuer verwenden, aber solche Programme benoetigen schnell Zusatzfunktionalitaeten, wie etwa die Beruecksichtigung der vorhergehenden Zeile oder Vergleiche mit dem vorhergehenden Treffer. All dies laesst sich mit awk wunderbar loesen. Darum finde ich hierfuer die Verwendung von awk sinnvoll. 6) Inhaltsverzeichnis formatieren --------------------------------- Ein Inhaltsverzeichnis, das mit dem vorangehenden Programm erstellt wurde, ist wenig schoen anzuschauen, da der Beginn der Ueberschriftstexte flattert: 1 Erste Hauptueberschrift 1.1 Erste Unterueberschrift 1.2 Zweite Unterueberschrift 1.2.1 Erste Unterunterueberschrift 1.2.2 Zweite Unterunterueberschrift 2 Zweite Hauptueberschrift 2.1 Erste Unterueberschrift Ein kleines awk-Programm loest dieses Problem: awk '{sprintf("%-10s", $1); print $0}' \ datei.inh > datei.inh2 Die Datei `datei.inh2' enthaelt das Inhaltsverzeichnis jetzt in der folgenden Form: 1 Erste Hauptueberschrift 1.1 Erste Unterueberschrift 1.2 Zweite Unterueberschrift 1.2.1 Erste Unterunterueberschrift 1.2.2 Zweite Unterunterueberschrift 2 Zweite Hauptueberschrift 2.1 Erste Unterueberschrift Weitere Formatierungen liessen sich ohne Probleme vornehmen, z.B. vor jeder Hauptueberschrift eine Leerzeile einfuegen, Steuerzeichen fuer das Einschalten anderer Schriftarten einbringen. Nunja, leider funktioniert auch dieses Beispiel nicht. Der Code erzeugt nicht die versprochene Ausgabe. Der `sprintf()'-Aufruf ist naemlich ein NOP, denn es fehlt die Zuweisung seines Rueckgabewertes: awk '{$1 = sprintf("%-10s", $1); print $0}' \ datei.inh > datei.inh2 Abgesehen davon ist der Code aber grundsaetzlich in Ordnung. Auch der Usecase ist gut. Awk ist dafuer das richtige Programm. Ich wuerde mir lediglich wuenschen, dass die zusaetzlichen Moeglichkeiten nicht nur erwaehnt werden, sondern auch gleich die Implementierungen dafuer geliefert werden. Ich liefere sie hiermit nach. Vor jeder Hauptueberschrift eine Leerzeile: awk '{$1 = sprintf("%-10s", $1); \ if ($1 !~ /\./) {print ""} \ print}' \ datei.inh > datei.inh2 Und Steuerzeichen fuer das Einschalten anderer Schriftarten, hier fuer troff (Nummern kursiv, Titel fett): awk '{$1 = sprintf("\\fI%-10s\\fP", $1); \ $2="\\fB" $2; $NR=$NR "\\fP"; \ print}' \ datei.inh > datei.inh2 7) Doppelte Zeilen suchen ------------------------- Manche aelteren Standardeditoren (vor allem aus dem Grossrechnerbereich) haben die unangenehme Eigenschaft, bei bestimmten Operationen unkontrolliert Zeilen zu verdoppeln. Das folgende Programm meldet doppelte Zeilen in einer Datei. awk '{v[NR]=$0; if (v[NR-1]==$0) print\ NR,"doppelt"; delete v[NR-1]}' Ich frage mich ja schon, was fuer Systeme der Autor genutzt hat, wenn der Editor nicht nur fuehrende Leerzeichen einfuegt, sondern auch noch *unkontrolliert* Zeilen verdoppelt! Wie soll man denn mit so unzuverlaessigen Programmen ueberhaupt arbeiten?! Wenn es aber schon so haarstraeubende Programme gibt, so faende ich es eigentlich recht interessant, zu erfahren, um welche genau es sich dabei handelt. Leider bleibt der Autor mit seinen Andeutungen unbestimmt. Aber nun gut, zum Code. Zuerst die gute Nachricht: Der Code funktioniert schonmal. Die schlechte Nachricht: Er ist unnoetig komplex und fehleranfaellig. Hier eine schriftliche Erklaerung des Programms. In jeder Zeile passiert folgendes: Der Zeileninhalt wird in ein Array am Index der Zeilennummer gespeichert. Dann wird geprueft, ob sich an der Stelle der vorigen Zeilennummer der gleiche Inhalt steht. Falls ja, wird eine Meldung ausgegeben. Anschliessend wird der Eintrag an der Stelle der vorigen Zeile geloescht. (Um genau zu sein: Es werden nicht doppelte Zeilen, sondern *aufeinanderfolgende* identische Zeilen gesucht!) Im Array `v' stehen nie mehr als zwei Eintraege -- die aktuelle und die vorherige Zeile. Die aktuelle Zeile ist aber ebenfalls als `$0' zugreifbar, folglich muss ueberhaupt nur ein einziger Wert gespeichert werden, naemlich die vorherige Zeile. Ein Array ist dafuer gar nicht noetig. Es geht auch so einfach: awk '{if (last==$0) {print NR,"doppelt"} last=$0}' Eigentlich wuerde ein awk-Programmierer das Programm aber so schreiben, dass das `if' auf die Bedingung-Aktion-Ebene gehoben ist: awk 'last==$0{print NR,"doppelt"} {last=$0}' Nicht nur, dass das Array mehr Aufwand fuer den Computer ist (knapp die doppelte Laufzeit gleichermassen bei 10.000 und 10.000.000 Zeilen, auf meinem Rechner), es ist auch komplexerer Code fuer den Programmierer, der Fehlerquellen zulaesst, die es im anderen Fall nicht gibt. Nehmen wir an, der Programmierer vertut sich bei der `delete'-Anweisung und loescht dort `v[NR+1]'. In diesem Fall wuerde das Programm den kompletten Input in den Arbeitsspeicher lesen. Bei der anderen Implementierung kann ein solcher Fehler ueberhaupt nicht passieren, da nur genau eine Variable verwendet wird, die jeweils ueberschrieben wird. Nun kann eine komplexere Loesung manchmal angebracht sein, naemlich dann, wenn sie einen Mehrwert bietet. Wie sieht es also aus, wenn man dem Programm mehrere Dateien uebergibt? Wenn die letzte Zeile der ersten Datei und die erste Zeile der zweiten Datei uebereinstimmen, ist das ja keine Dopplung. Hier ist seine Umsetzung aber genauso schlecht wie meine. Beide melden fuer den Fall eine Dopplung. Loesen laesst sich diese indem man statt `NR' die Variable `FNR' verwendet. Dies ist im vorgeschlagenen Programm einfach: awk '{v[FNR]=$0; if (v[FNR-1]==$0) print \ FNR,"doppelt"; delete v[FNR-1]}' In meinem Programm wuerde man dies einfach durch einen weiteren Block loesen, in dem man `last' loescht, falls wir uns in der ersten Zeile der Datei befinden: awk 'FNR==1{last=""} \ last==$0{print FNR,"doppelt"} {last=$0}' Der Usecase an sich ist angemessen. Dieses Problem kann man gut mit awk loesen. Die Ausgabe wuerde man unter Unix aber typischerweise so gestalten, dass man die Zeile selbst mit ausgibt. Das Wort ``doppelt'' liefert keinen Mehrwert, wenn eh alle Ausgaben doppelte Zeilen sind. Folglich kann man an seiner Stelle etwas Sinnvolleres, wie den Zeileninhalt selbst, ausgeben: awk 'last==$0{print NR ": " $0} {last=$0}' Dies liesse sich ebenfalls auf folgende Weise erreichen: cat -n | uniq -f 1 -d Aber sei's drum; awk finde ich hier schon ein angemessenes Programm, gerade auch wegen der Moeglichkeit, die Verarbeitung Dateiweise (`FNR') durchzufuehren, was bei cat/uniq eine Shellschleife braeuchte. 8) Teil einer Datei ausgeben ---------------------------- Sie haben ein ausfuehrliches Gutachten geschrieben, aus dem Sie heute aber nur den Abschnitt 3.4 benoetigen. Das folgende Programm schreibt den Abschnitt 3.4 aus der Datei `gutacht' in die Datei `gutacht_3.4'. Die Operation gelingt unter der Voraussetzung, dass ein Abschnitt 3.4 vorhanden und mit einer Klassifikation gekennzeichnet ist und auf den gewuenschten Abschnitt 3.4 der Abschnitt 3.5 folgt. awk '/^ *3.4/,/^ *3.5/' gutacht > gutacht_3.4 Ein sinnvoller Usecase, den man immer dann mit awk erledigen wuerde, wenn man gerne EREs haette und nicht nur die BREs von sed. Die Umsetzungen in awk und sed sind fast identisch. Leider ist das Programm etwas ungenau, denn statt auf den Punkt zu matchen, matcht es auf ein beliebiges Zeichen. Eine Zeile die mit ``375 Meter'' beginnt wuerde die Aussage vorzeitig beenden. Treffender wird es wenn man wirklich auf die Punkte matcht: awk '/^ *3\.4/,/^ *3\.5/' gutacht >gutacht_3.4 Weiterhin finde ich schade, dass hier nicht die gleiche Klassifikationsdefinition (d.h. der gleiche Regulaere Ausdruck) wie in der Inhaltsverzeichnisaufgabe verwendet wird. Dort wurde auf Leerzeichen nach der Klassifikations geprueft; hier wird zusaetzlich fuehrender Whitespace erlaubt. Das haette man doch abgleichen koennen. 9) Datensaetze auf Vollstaendigkeit pruefen ------------------------------------------- Eine Datei `bestand' enthaelt Datensaetze, von denen jeder acht Eintraege enthalten soll. Ein einfaches awk-Programm meldet Datensaetze, die weniger oder mehr als die erforderlichen acht Eintraege enthalten. awk 'NF != 8 {print NR}' bestand Hier faellt's mir schwer, etwas zum Kritisieren oder Verbessern zu finden. Das einzige, was ich moeglicherweise aendern wuerde -- aber das haengt von den Daten selbst ab -- ist, dass ich vielleicht den Zeileninhalt mit ausgeben wuerde. Zumindest in meiner Arbeitsumgebung kann ich mit Zeileninhalten mehr anfangen als mit Zeilennummern. Aber das mag bei seinem Grossrechner-Standardeditor anders gewesen sein. Bei ed sind Zeilennummern ja auch hilfreiche Einstiegspunkte. 10) Dateien zusammenfuegen -------------------------- In manchen Faellen kann es hilfreich sein, viele kleine Dateien in eine grosse Datei zusammenzupacken, z.B. fuer einen Datentransfer oder um sie auf einem Datentraeger zu archivieren. Diese Aufgabe kann mit awk sehr einfach geloest werden. awk 'FNR = 1 {print "### " FILENAME}{print $0}'*\ > datei Ein Programm, das die so erstellte Datei wieder in die Ausgangsdateien zerlegt, ist nur unwesentlich laenger. Sie finden es in Kapitel 5 dieses Buches (siehe Abschnitt ``Dateiverwaltung und -bearbeitung -- Daten zusammenfassen und wieder auseinander nehmen''). Dieses Programm ist so leider nicht lauffaehig. Es erzeugt einen Syntaxfehler: awk: cmd. line:1: FNR = 1 {print "### " FILENAME}{print $0}* awk: cmd. line:1: ^ syntax error Der Grund ist fehlender Whitespace vor dem Stern, damit er auf die Dateinamen im aktuellen Verzeichnis matchen kann. So wie er hier steht, gehoert er zum awk-Programm und bleibt literal stehen, weil er auf nichts expandieren kann. Semantisch ist das Programm ebenfalls falsch, denn es fehlt ein Gleichheitszeichen, um aus der Zuweisung ``FNR = 1'' einen boolschen Ausdruck ``FNR == 1'' zu machen. So wie der Code abgedruckt ist, fuehrt es dazu, dass die Dateinamensheaderzeile vor *jeder* Zeile eingefuegt wird, statt nur vor der ersten Zeile jeder zu packenden Datei. Korrekt ist der Code so: awk 'FNR == 1 {print "### " FILENAME}{print $0}' * \ > datei Ich haette ihn so geschrieben: awk 'FNR==1{print "### " FILENAME} {print}' * >datei Bzw. so: awk 'FNR==1{print "### " FILENAME} 1' * >datei Relevant zu erwaehnen waere gewesen, dass sich in den Dateien natuerlich keine mit drei Hashes beginnenden Zeilen befinden duerfen. Eine Pruefung dafuer einzubauen, waere noch besser gewesen: awk 'FNR==1{print "### " FILENAME} /^### /{ print "Error: Zeile beginnt mit ###" \ > "/dev/stderr" exit 1 } {print}' * >datei Oder noch besser ein Escaping-Mechanismus: awk 'FNR==1{print "### " FILENAME} /^### /{ $0 = ">" $0 } {print}' * >datei ... der dann natuerlich beim Auspacken auch wieder rueckgaengig entfernt werden muss. Unter Unix ist dieses awk-Programm unnoetig, weil tar die Aufgabe viel besser und portabel erledigt. Aber zur Demonstration kann man das Beispiel schon anfuehren. An ihm kann man eben auch das Thema Escaping aufgreifen. 12) Ueberlange Zeilen auf zwei Zeilen aufteilen ----------------------------------------------- Die meisten zeilenorientierten Texteditoren koennen nur Zelien bis zu einer maximalen Laenge bearbeiten. Wenn eine Datei mit laengere[n] Zeilen bearbeitet werden soll, stellt sich das folgende Problem: - Um Zeilen, die zu lange sind, auf die erlaubte Laenge zu kuerzen, muss die Datei in den Texteditor geladen werden. - Um die Datei aber laden zu koennen, muessen zuvor die ueberlangen Zeilen gekuerzt werden. Mit dem folgenden awk-Programm kann dieses Problem geloest werden. Es teilt alle Zeilen der Eingabedatei `dat1', die mehr als 80 Zeichen enthalten, auf zwei Zeilen auf und schreibt das Ergebnis in die Datei `dat2': awk 'length> 80{print substr($0,1,80)"\n"\ substr($0,81); next}{print}' dat1 > dat2 Die gute Nachricht ist, dass das Programm funktioniert. Es tut was es -- laut Beschreibung -- tun soll. (Statt dem Begriff ``kuerzen'' waere es schoener gewesen, den Begriff ``umbrechen'' zu verwenden, weil hier ja keine Daten entfernt, sondern nur anders angeordnet werden.) Was mich an dem Beispiel stoert ist seine Beschraenktheit auf nur genau einen Umbruch, weil dadurch gar nicht sichergestellt ist, dass danach keine Zeile mehr laenger als 80 Zeichen ist. Man muss das Programm ggf. mehrmals ausfuehren (mit angepassten Dateiargumenten) bis man zu einem Zustand kommt, in dem der Editor die Datei oeffnen kann. (Dieser ist dann erreicht, wenn sich die Datei durch das Programm nicht mehr aendert.) Dabei waere es gar nicht aufwaendiger, beliebig lange Zeilen so oft umzubrechen, bis alle kurz genug sind: awk '{while (length>80) {print substr($0,1,80); $0=subbstr($0,81)} print}' dat1 >dat2 Unter Unix bietet das Standardprogramm `fold' genau diese Funktionalitaet, was die Praxisrelevanz des Beispiels in Frage stellt. Schluss ------- Ein Buch sollte gute Beispiele praesentieren, weil diese immer eine Wirkung auf und eine Vorbildfunktion fuer die Leser haben. Es sollten sogar die besten Beispiele praesentiert werden, die man sich nur vorstellen kann. Sie muessen das zu erreichende Ideal darstellen, nicht die (traurige) Realitaet. Dennoch halte ich nichts von weltfremden Sandkasten-Beispielen, nein, es muss Code aus der Realitaet sein, aber nicht auf Durchschnittsrealitaetsniveau, sondern liebevoll perfektioniert. Wenn man lehrt ist man Vorbild; dieser Verantwortung muss man gerecht werden. Das betrifft insbesondere auch so ``Kleinigkeiten'' wie den Whitespace. In manchen Beispielen hier ist er ohne System verteilt. Die Sorgfalt bei der Syntax (z.B. Konsistenz von Whitespace und Einrueckung) sind ein, meiner Erfahrung nach, gutes Indiz fuer die Sorgfalt bei der Semantik. Ist die Form ungenau, so ist wahrscheinlich auch die Problemloesung ungenau. Dies war bei mehreren Beispielen erkennbar. Albert Einstein hat gesagt: ``Es gibt keine andere vernünftige Erziehung, als Vorbild zu sein, wenn es nicht anders geht, ein abschreckendes.'' Das abschreckende Vorbild ist aber nur dann ein wertvolles, wenn es als abschreckend erkannt wird. Ich hoffe, dass durch meine Analyse dieses Vorbild als abschreckend wahrgenommen wird, damit die Leser davon lernen koennen und dadurch das Buch wertvoll wird. Ich moechte dem Autor und Verlag dafuer danken, die Basis fuer diese Analyse geboten zu haben. Ohne die Grundlage dieser Beispiele waere es mir nicht moeglich gewesen, den Mehrwert zu schaffen der nun in Form dieser Analyse vorliegt. Betont moechte ich darauf hinweisen, dass es keinesfalls meine Absicht ist, Autor oder Verlag anzugreifen -- es gibt immer Gruende, warum etwas so geworden ist, wie es ist --, sondern ich will mithelfen gemeinsam daraus zu lernen, damit wir nicht die gleichen Fehler erneut machen. Im Uebrigen: ein hervorragendes Vorbild findet man in ``The AWK Programming Language'' von Aho, Weinberger und Kernighan.