Aufmacher Artikel

Bitte übersetzen Sie!

The Answer Girl

01.06.2001 Solange man online ist, halten sich die Sprachprobleme in Grenzen: Web-Wörterbücher wie dict.leo.org helfen in meistens akzeptabler Geschwindigkeit über die Hürden des fehlenden (Englisch-) Wortschatzes hinweg. Doch wehe, man ist weder mit Standleitung noch Flatrate gesegnet: Schon ärgert man sich, das Regal mit den papiernen Wörterbüchern am anderen Ende des Raums aufgestellt zu haben.

Answer Girl

Dass der Computeralltag auch unter Linux des Öfteren für Überraschungen gut ist, ist eher eine Binsenweisheit: Immer wieder funktionieren Dinge nicht oder nicht so, wie eigentlich angenommen. Das Answer-Girl im LinuxUser zeigt, wie man mit solchen Problemchen elegant fertig wird.

Grafische Helferlein wie das im LinuxUser 03/2001 auf S. 66 f. vorgestellte qtrans oder das K-Tool kdict aus Heft 09/2000, S. 74 f., bieten auch offline Abhilfe, doch leider lassen die dort verwendeten Wörterbuchformate kein einfaches Stöbern mit less & Co. auf der Kommandozeile zu. Für das DICT-Protokoll gibt es zwar auch das Kommandozeilentool dict, allerdings hat DICT trotz seines offenen Formats einen großen Nachteil: Ohne den dictd-Server geht gar nichts.

Alles in allem nicht gerade ideal für Anwender, die ihre Wörterbücher gern auch zum Stöbern verwenden oder im Schweiße des eigenen Angesichts erstellte Vokabellisten auch nach dem Paukstress als Nachschlagewerk weiter nutzen möchten. Solange es bei lateinischen Buchstaben bleibt, sind reine ASCII-Dateien hier ungeschlagen: Mit less durchstöbert, lässt sich mit dem less-Befehl /suchbegriff gezielt nach bestimmten suchbegriffen suchen.

Wanted: ASCII-Wortlisten

Selbst wenn Sie im Netz der Netze mittlerweile nicht mehr so einfach aufzufinden sind: Wer nach gesammelten englisch-deutschen Wortlisten fahndet, wird z. B. unter http://www.wh9.tu-dresden.de/~heinrich/dict/dict_leo_ftp/leo_ftp/ fündig.

Einmal herunter geladen und in ein gemeinsames Verzeichnis kopiert – wer root-Rechte hat, wird z. B. /usr/dict/eng_deu erstellen – kann das Stöbern beginnen (Leserechte vorausgesetzt):

[trish@lillegroenn ~]$ cd /usr/dict/eng_deu
 [trish@lillegroenn eng_deu]$ less *

Irgendwann beim Buchstaben z meldet sich less dann in der letzten Zeile zu Wort:

(END) - Next: EXERCISE.VOK

Wie um alles in der Welt kommen wir jetzt in die nächste Datei EXERCISE.VOK? Ein h zeigt zum Glück eine Hilfeseite an, aus der wir lesen:

CHANGING FILES[…]
 :n   *  Examine the (N-th) next file from the command line.
 :p   *  Examine the (N-th) previous file from the command line.

Das less-Kommando :n bringt uns also zur nächsten Datei, während wir mit :p jeweils eine Datei zurück springen können. Leider beschränken sich die Vorwärtssucherei mit /suchbegriff und die Rückwärtssuche mit ?suchbegriff immer auf das aktuell angezeigte File. Doch auch hier weiß h (oder die Man Page) Abhilfe:

SEARCHING[…]
         Search patterns may be modified by one or more of:[…]
         ^E or *  Search multiple files (pass thru END OF FILE).

Zum Ausprobieren schließen wir den Hilfemodus mit q, gehen mit :x zurück in die erste Datei und darin mit 1G (Goto line 1) in die erste Zeile. Wenn wir jetzt statt /yesterday /*yesterday eingeben und mit n jeweils zur nächsten Fundstelle von yesterday springen, ist am Ende einer Datei nicht mehr Schluss, sondern wir suchen uns durch sämtliche auf der Kommandozeile angegebenen Dateien. (Nach der Eingabe des Sternchens meldet less mit EOF-ignore in der letzten Statuszeile, dass es für diese Suche gedenkt, das Ende einer Datei (End of file") zu ignorieren.)

Nicht stöbern, sondern suchen

Nun war das Stöbern zwar ein wichtiges Argument für die ASCII-Vokabellisten, doch auf die gezielte Suche wollen wir auch nicht verzichten. Zu diesem Zweck ist grep unser Freund:

[trish@lillegroenn eng_deu]$ grep yesterday *
 BOOK.VOK:yesterday gestern
 EXERCISE.VOK:gestern - yesterday[…]
 eng2ger.vok:gestern – yesterday[…]

So sehr wir uns sonst darüber freuen, dass grep uns die Fundstelle nennt – für unsere Nachschlagezwecke interessiert es uns nicht gerade brennend, in welcher Datei grep fündig wurde. Zum Glück erklärt man grep

-h, --no-filename
     Suppress the prefixing of filenames on output
     when multiple files are searched.

…, dass sich das Nennen des Dateinamens z. B. mit dem Flag -h abschalten lässt:

[trish@lillegroenn eng_deu]$ grep -h yesterday *
 yesterday gestern
 gestern - yesterday[…]
 gestern – yesterday[…]

Doch das lässt einen Nachteil des auf mehrere, zum Teil thematische ASCII-Dateien mit den Dateinamenendungen .vok oder .VOK verteilten Vokabulars noch deutlicher hervortreten: Die verschiedenen Dateien benutzen unterschiedliche Konventionen, um Phrase und Übersetzung voneinander zu trennen. Um Dubletten herauszufiltern, bleibt nur eines: Wir müssen die Dateien alle auf eine Konvention trimmen.

Gleichmacherei

eng2ger.vok trennt die deutschen Vokabeln durch zwei Bindestriche und jeweils ein Leerzeichen davor und danach von ihren englischen Übersetzungen:

erst gestern – only yesterday

Da diese Datei bei Weitem die größte ist, bietet es sich an, ihre Konvention auf die anderen Files zu übertragen.

Bei EXERCISE.VOK ist das nicht so schwer: Diese Datei belässt es bei einem Strich (-) zwischen den Leerzeichen, den wir mit sed schnell ersetzen:

[trish@lillegroenn eng_deu]$ sed -e "s/ - / – /" EXERCISE.VOK > EXERCISE.VOK_ 

Der sed-Befehl s substituiert schlicht und einfach das erste Vorkommnis von LeerzeichenMinusLeerzeichen in jeder Zeile mit LeerzeichenMinusMinusLeerzeichen. Das Ergebnis dieses Befehls, angewendet auf EXERCISE.VOK, erhalten wir eigentlich auf der Standardausgabe. Doch da wir es lieber in einer Datei sehen, leite wir mit > die Ausgabe in die Datei EXERCISE.VOK_ um. Nachdem wir uns vergewissert haben, dass die neue Datei vernünftig aussieht, reicht ein

[trish@lillegroenn eng_deu]$ mv EXERCISE.VOK_ EXERCISE.VOK

um die alte mit der neuen Datei zu überschreiben.

Die Datei BOOK.VOK stellt schon höhere Ansprüche: Hier dient ein einfaches Leerzeichen als Trennzeichen:

yesterday gestern

Damit es keine Verwechslungen mit Wortzwischenräumen in Wendungen gibt, sind diese durch einen Unterstrich gekennzeichnet, der als Wortbestandteil glücklicherweise nicht vorkommt:

yearn sich_sehnen

Hier müssen wir also zweimal ersetzen: das jeweils erste Leerzeichen durch LeerzeichenMinusLeerzeichen (s/ / -- /) und global jegliches Vorkommen von _ durch Leerzeichen (s/_/ /g). Kombiniert sieht das so aus:

[trish@lillegroenn eng_deu]$ sed -e "s/ / – /" -e "s/_/ /g" BOOK.VOK > BOOK.VOK_

Prüfen und Vergleichen

Bevor wir BOOK.VOK mit BOOK.VOK_ überschreiben, möchten wir die neue Datei prüfen, also mit dem Original vergleichen. Doch diff eignet sich hierfür nicht, da es alle Zeilen ausgibt, die verschieden sind, und das sind nunmal alle… Was wir brauchen, ist ein wortbasiertes diff: wdiff. Wenn's die Distribution nicht mitliefert, ist es z. B. unter http://rpmfind.net/linux/rpm2html/search.php?query=wdiff oder http://packages.debian.org/stable/text/wdiff.html erhältlich.

[trish@lillegroenn eng_deu]$ wdiff --help
 Usage: wdiff [OPTION]… FILE1 FILE2[…]
   -3, --no-common      inhibit output of common words

Mit der Option -3 lässt sich also vermeiden, dass wdiff Wörter ausgibt, die gleich geblieben sind. Wenn wir die ganze Ausgabe noch durch less schicken, verhindern wir auch, dass uns beim Durchschauen etwas durch die Lappen geht:

[trish@lillegroenn eng_deu]$ wdiff -3 BOOK.VOK BOOK.VOK_ | less[…]
 =========================================
  {+--+}
 =========================================
  [-du_kannst-] {+-- du kannst+}[…]

wdiffs Ausgabe ist zugegebenermaßen etwas gewöhnungsbedürftig: Die =-Zeile fungiert lediglich als Trennzeile. In [- -] stehen Strings aus BOOK.VOK, die in BOOK.VOK_ durch die Zeichenkette in der {+-Klammer ersetzt wurden. Das {+--+} bedeutet, dass in BOOK.VOK_ einfach zwei Minuszeichen hinzugekommen sind – Leerzeichen sind für das wortbasierte diff vernachlässigbar.

Besser lesbar wird die Ausgabe im sogenannten less-Modus, der mit less eigentlich nicht viel zu tun hat. Aber immerhin,

[trish@lillegroenn eng_deu]$ wdiff -3l BOOK.VOK BOOK.VOK_ | less[…]
 =========================================
  –
 =========================================
  du_kannst – du kannst[…]

verzichtet auf die ungewohnte Klammerung und macht die Ausgabe daher leichter lesbar.

Allerdings haben wir keine Lust, durch die gesamte less-Ausgabe zu gehen, und überlegen uns Folgendes: Wenn wir alles richtig gemacht haben, wirft wdiff -3 genausoviele ---Zeilen aus, wie BOOK.VOK (und BOOK.VOK_) Zeilen (lines – -l) hat:

[trish@lillegroenn eng_deu]$ wc -l BOOK.VOK BOOK.VOK_
   29018 BOOK.VOK
   29018 BOOK.VOK_
   58036 total

Wenn wir aus der wdiff-Ausgabe alle störenden Trennzeilen heraus filtern, sollten wir eigentlich ebenfalls auf 29018 Zeilen kommen (grep -v sucht all die Zeilen heraus, die kein == enthalten):

[trish@lillegroenn eng_deu]$ wdiff -3l BOOK.VOK BOOK.VOK_ |grep -v "=="| wc -l
   29023

So ganz wie geplant lief das also nicht – wo kommen die fünf Zeilen zuviel her? Clever, wie wir sind, lassen wir uns einfach all die Zeilen anzeigen, die kein Doppelminus enthalten:

[trish@lillegroenn eng_deu]$ wdiff -3l BOOK.VOK BOOK.VOK_ | grep -v "==" | grep -v "--"| wc -l
 Usage: grep [OPTION]… PATTERN [FILE]…
 Try `grep --help' for more information.
       0

Doch da haben wir wohl was falsch gemacht… Natürlich: Minus-Zeichen dienen ja als Kennzeichen für die Shell, dass dahinter eine Option kommt, und die Stärke der doppelten Anführungszeichen reicht nicht aus, um unser Minus-Suchmuster vor der Shell zu verbergen.

Zum Glück erinnern wir uns an den alten Bash-Trick, einem Befehl mit einem -- zu sagen, dass dahinter keine weiteren Optionen kommen:

[trish@lillegroenn eng_deu]$ man bash[…]
 OPTIONS[…]
    -  A single - signals the end of options and disables fur­
       ther  option processing.  Any arguments after the - are
       treated as filenames and arguments.  An argument of  –
       is equivalent to an argument of -.[…]

Mit einem

[trish@lillegroenn eng_deu]$ wdiff -3l BOOK.VOK BOOK.VOK_ | grep -v "==" | grep -v – "--"| wc -l
       5

kommen wir also tatsächlich auf die vermissten fünf Zeilen. Doch wo kommen die her?

[trish@lillegroenn eng_deu]$ wdiff -3l BOOK.VOK BOOK.VOK_ | grep -v "==" | grep -v – "--"
 arrow_keys
 sensing_mark

Drei Leerzeilen, die wdiff irgendwie eingebaut hat, aber was ist mit arrow_keys und sensing_mark? Derselbe Befehl ohne die l-Option für wdiff gibt Auskunft, und

[trish@lillegroenn eng_deu]$ wdiff -3l BOOK.VOK BOOK.VOK_ | less

lässt uns mit dem less-Befehl /arrow_keys nach der entsprechenden Stelle im Vergleich fahnden. Siehe da:

 [-arrow_keys-]
 {+arrow keys --+}

Schuld (auch an den Leerzeilen) trägt eindeutig wdiff.

Bei all dem Hin und Her hätten wir beinahe vergessen, warum wir wdiff überhaupt rausgekramt haben: Wir wollten prüfen, ob in den Zeilen, in denen wir Unterstriche ersetzt haben, alles glatt gegangen ist. Da nehmen wir lieber wdiff ohne die l-Option her, denn da können wir alle Zeilen ausschließen, in denen ein {+--+} vorkommt:

[trish@lillegroenn eng_deu]$ wdiff -3 BOOK.VOK BOOK.VOK_ | grep -v "==" | grep -v "{+--+}" | less

Alles in Ordnung? Dann überschreiben wir einfach das alte BOOK.VOK mit dem konvertierten Inhalt aus BOOK.VOK_:

[trish@lillegroenn eng_deu]$ mv BOOK.VOK_ BOOK.VOK 

Grep und paste

Als hätten wir uns nicht schon genug gemüht, stellt uns technik.vok vor eine ungleich schwierigere Aufgabe: Hier steht Original und Übersetzung jeweils in einer eigenen Zeile, von den restlichen Vokabeln ist das Paar durch jeweils eine Leerzeile abgetrennt:

 Ab-; Abfall
 waste
 abfuehren
 discharge[…]

Mit sed auf einer Kommandozeile wird das nichts mehr, denn hier müssen wir Zeilenumbrüche durch -- ersetzen und zusätzlich noch Leerzeilen eliminieren. Auch mit perl wird es schwierig, einen noch halbwegs verständlichen Einzeiler dafür zu bauen. Doch zum Glück ist die Datei so regelmäßig aufgebaut, dass – wenn wir einmal die Leerzeilen entfernt haben – immer eine ungerade und die darauf folgende gerade Zeile zusammengehören.

Die Leerzeilen bekommen wir weg, indem wir mit grep all jene Zeilen heraussuchen, in denen mindestens ein Buchstabe a-z und/oder A-Z vorkommt:

[trish@lillegroenn eng_deu]$  grep [a-zA-Z] technik.vok

Jetzt wird es etwas schwieriger. Doch da erinnern wir uns an das cut-Kommando, mit dem sich Spalten aus Textdateien extrahieren lassen. Wenn es cut gibt, muss es doch auch ein paste geben, dass mehrere Spalten zu einer Datei zusammenfügt. Tatsächlich werden wir mit man paste fündig.

Mit -d können wir einen Spaltentrenner angeben – leider nur einbuchstabig, aber gut, das können wir später noch mit sed ersetzen. Wichtig ist nur, dass der Delimiter nicht in technik.vok vorkommt. Wie wäre es mit #? Lassen Sie uns nachzählen (engl. count"):

[trish@lillegroenn eng_deu]$  grep -c "#" technik.vok
 0

Genau 0 Mal kommt das Hash-Zeichen ("#") in dieser Wörterbuchdatei vor und eignet sich daher hervorragend als temporärer Spaltentrenner für paste.

Der Rest ist ganz einfach: paste will als Argumente lediglich die beiden Dateien haben, die als erste und weitere Spalte(n) dienen. Nun haben wir zwar keine Dateien, aber die Man Page verrät, dass paste auch mit der Standardeingabe (z. B. aus der Pipe von grep) zufrieden ist, wenn wir statt eines Dateinamens ein - einsetzen.

Eigentlich können wir mit der Standardeingabe STDIN (standard input") ganz glücklich sein; diese hat nämlich die schöne Eigenschaft, dass eine Zeile aus STDIN verschwindet, sobald sie einmal ausgelesen wurde. Wenn wir paste in einem zugegebenermaßen üblen Hack zweimal STDIN unterschieben, bekommen wir genau den Effekt, den wir wollen: In der ersten Spalte stehen die ungeraden, in der zweiten Spalte die geraden Zeilen:

[trish@lillegroenn eng_deu]$  grep [a-zA-Z] technik.vok | paste -d "#" - -
 Ab-; Abfall#waste
 abfuehren#discharge[…]

Das Hash-Zeichen daraus zu entfernen, ist eine unserer leichtesten Übungen, und das Ergebnis leiten wir gleich in die Datei technik.vok_ um:

[trish@lillegroenn eng_deu]$  grep [a-zA-Z] technik.vok | paste -d "#" - - | sed -e "s/#/ – /" > technik.vok_

Das Ergebnis technik.vok_

Ab-; Abfall – waste
 abfuehren – discharge[…]

… kann sich und damit auch gleich in technik.vok umbenennen lassen.

Damit hätten wir eine genügende Auswahl Wörterbuchdateien (BOOK.VOK, EXERCISE.VOK, eng2ger.vok und technik.vok) am Platz – die Umwandlung der restlichen überlasse ich Ihrem Erfindungsreichtum – und können uns endlich einem kleinen Skript zuwenden, das die Übersetzung auf der Kommandozeile eingegebener Wörter übernimmt.

Einmal umdrehen

Von den hier benutzten vier Vokabeldateien weist BOOK.VOK einen gravierenden Unterschied zu den anderen auf: Der englische Begriff steht links, die deutsche Entsprechung rechts. Da das wb-Skript aus Listing 1 nicht erkennt, dass beispielsweise gestern -- yesterday aus eng2ger.vok und yesterday -- gestern aus BOOK.VOK für unsere Zwecke eine Dublette ist, ist es vermutlich am einfachsten, die Spalten in BOOK.VOK einfach umzudrehen.

Wie bei all den in diesem Answer-Girl vorgestellten Textmodifikationsübungen führen auch hier mehrere Wege zum Ziel; einige sollen an dieser Stelle exemplarisch aufgelistet werden.

Cut & Paste

Mit cut lassen sich Spalten aus einer Textdatei extrahieren, die mit paste wieder – auch in umgekehrter Reihenfolge – zusammengefügt werden können. Den Spaltentrenner geben wir explizit mit der Option -d (delimiter") an. Leider darf dieser nur ein Zeichen, keine Zeichenkette sein, und das macht das Ganze etwas umständlich:

[trish@lillegroenn eng_deu]$ sed -e "s/ – /%/" BOOK.VOK | cut -d "%" -f 1 > /tmp/BOOK.VOK.1
 [trish@lillegroenn eng_deu]$ sed -e "s/ – /%/" BOOK.VOK | cut -d "%" -f 2 > /tmp/BOOK.VOK.2
 [trish@lillegroenn eng_deu]$ paste -d "%" /tmp/BOOK.VOK.2 /tmp/BOOK.VOK.1 | sed -e "s/%/ – /" > /tmp/BOOK.VOK.paste

In den ersten beiden Zeilen ersetzen wir jeweils den echten Spaltentrenner -- durch das Arbeitstrennzeichen %. Zeile eins holt dann mit cut -f 1 alles heraus, was links neben dem Trennzeichen steht, und schreibt es in die temporäre Datei /tmp/BOOK.VOK.1. Dasselbe geschieht mit der zweiten Spalte (-f 2) rechts vom Trennzeichen in Zeile zwei – die Ausgabe dieser Ausschneideaktion mit cut landet in /tmp/BOOK.VOK.2. Wenn wir paste in der dritten Zeile als erstes Argument die zweite und als zweites Argument die erste temporäre Datei mitgeben, haben wir die Spalten aus BOOK.VOK vertauscht. Nun nur noch die Prozentzeichen wieder durch -- ersetzen und das Ergebnis der Umtauschaktion in /tmp/BOOK.VOK.paste speichern. Ist alles glatt gegangen, kann die Originaldatei damit überschrieben werden.

Perlen und Ausdrücke

Es geht natürlich auch weniger umständlich – doch dann gelangen wir in den Einflussbereich eigenständiger Skriptsprachen wie z. B. Perl. perl lässt sich mit der Option -p ganz gut als mächtigerer sed-Ersatz benutzen. Wie bei sed leitet die Option -e (execute") ein auf der Kommandozeile auszuführendes perl-Kommando ein.

[trish@lillegroenn eng_deu]$ perl -pe 's/(^.*)( – )(.*$)/$3$2$1/' BOOK.VOK > /tmp/BOOK.VOK.perl 

Ersetzt werden soll alles (.*) vom Anfang (^) einer Zeile bis zum Ende ($) durch eine umgeordnete Version. Damit der Zeileninhalt nicht verloren geht, speichern wir ihn in runden Klammern zwischen: den Anfang der Zeile vor dem Trennstring -- im ersten Puffer, -- im zweiten und den Rest bis zum Zeilenende im dritten Puffer. Ersetzt wird das Ganze jetzt durch den Inhalt des dritten Puffers ($3), gefolgt vom Trennstring aus dem zweiten ($2) und dem ehemaligen Zeilenanfang aus dem ersten Puffer ($1).

Beachten Sie, dass Sie das Perl-Substitute-Kommando in einfache Anführungszeichen (') setzen. Doppelte Anführungszeichen führen dazu, dass die Shell annimmt, mit $3$2$1 seien die Inhalte von Shell-, nicht Perl-Variablen gemeint.

Als wär's kein Problem

Der meiner Ansicht nach eleganteste Weg jedoch führt über awk. Im Gegensatz zu paste kommt dieses Tool nämlich auch mit Mehrzeichen-Spaltentrennern klar. Allerdings gibt man den Delimiter hier mit der Option -F (Field separator") an.

[trish@lillegroenn eng_deu]$ awk -F " – " '{print $2 " – " $1}' BOOK.VOK > BOOK.VOK.awk 

Das awk-"Programm" in einfachen Hochkommata besteht normalerweise aus einem Muster, auf das ein Kommandoblock in geschweiften Klammern angewandt wird. Da wir die gesamte Datei meinen, brauchen wir kein explizites Muster angeben und begnügen uns mit dem Klammerblock.

Darin weisen wir awk an, den Inhalt der zweiten Spalte ($2), dann den Trennstring -- und zum Schluss den Inhalt der ersten Spalte auszugeben.

Such mir mal

Wie (fast) jedes Shellskript beginnt es mit der Angabe, welche Shell wir verwenden. Natürlich die, mit der wir uns am Besten auskennen, und das wird meist die Linux-Standardshell bash sein:

#!/bin/bash -vx

Beim Entwickeln eines Skripts passieren oft Fehler, weshalb wir zunächst einmal die Debug-Optionen -vx einschalten.

Vorausgesetzt, in /usr/dict/eng_deu liegen nur konvertierte Wörterbuchdateien, halten wir dieses Wörterbuchverzeichnis in der Variable WBDIR fest:

WBDIR=/usr/dict/eng_deu

Wie bei jedem Skript, das für mehr als eine Person bestimmt ist, beginnen wir mit einer Aufrufprüfung: Wenn die Benutzerin mehr oder weniger als einen Suchbegriff als Argument eingibt (also ungleich (not equal") einen), …

if [ $# -ne 1 ]; then

… spucken wir einfach aus, wie unser Skript bedient werden möchte:

echo "Usage: $0 string"

Netterweise merkt sich ein Shellskript in der Variablen #, mit wievielen Argumenten es aufgerufen wurde. In der Variablen 0 (null) steckt das nullte Argument, also der Kommandoname selbst (ggf. mit angegebenem Pfad).

Im anderen Fall …

else

… suchen wir in den Vokabellisten im Verzeichnis $WBDIR nach dem ersten Kommandozeilenargument ($1):

        grep -hw "$1" $WBDIR/*

Mit der "Wort-Option" -w sorgen wir dafür, dass grep nur dann etwas ausgibt, wenn das Suchwort als solches (und nicht etwa als Bestandteil eines anderen Worts) in den Vokabellisten auftaucht.

Um Tippfehler bei der Groß- und Kleinschreibung auszuschließen, können wir grep auch noch dazu zwingen, Unterschiede in Groß- und Kleinbuchbuchstaben zu ignorieren:

        grep -hwi "$1" $WBDIR/*

… womit wir eigentlich schon fertig wären und die if-Konstruktion schließen können:

fi

Ausführbarkeitsrechte an unser wb-Skript vergeben …

[trish@lillegroenn /tmp]$ chmod ugo+x wb

… und testen:

[trish@lillegroenn /tmp]$ ./wb 
 #!/bin/bash -vx
 WBDIR=/home/trish/dict
 + WBDIR=/home/trish/dict
 if [ $# -ne 1 ]; then
         echo "Usage: $0 string"
 else
         grep -hwi "$1" $WBDIR/*
 fi
 + [ 0 -ne 1 ]
 + echo Usage: ./wb string"
 Usage: ./wb string

Dank der Geschwätzigkeitsoption -v ("verbose") zeigt die Bash jede einzelne Zeile an, die sie auszuführen gedenkt. Die Zeilen mit dem einleitenden Plus haben wir hingegen der Ausführlichkeitsoption -x ("extensive") zu verdanken, die jedes Mal auch angibt, was die Shell intern wirklich "sieht", wenn sie alle Ersetzungen vorgenommen (z. B. die Inhalte von Variablen ausgelesen) hat. Zum guten Schluss – und leider nicht besonders gekennzeichnet – finden wir in dem Wust natürlich auch noch die Ausgabe, die wir ohne Debug-Optionen zu Gesicht bekommen hätten, hier: Usage: ./wb string.

Auch die Variante mit einem Suchwort funktioniert:

[trish@lillegroenn /tmp]$ ./wb yesterday
 […]
 yesterday – gestern
 only yesterday – erst gestern
 yesterday – gestern
 gestern – yesterday
 vorgestern – the day before yesterday
 […]

Ohne Doppelgänger

Diese Ausgabe zeigt deutlich, dass wir mit dem Skript noch etwas vorhaben: Wir wollen die Dubletten loswerden. Eigentlich eine einfache Sache: die Ausgabe mit sort sortieren (dank -f – "fold" – mit gleicher Wertigkeit für Groß- und Kleinbuchstaben) und mit uniq die Doppelgänger rauswerfen:

        grep -hwi "$1" $WBDIR/* | sort -f | uniq

Leider stimmt damit irgendwas nicht, denn der Testlauf ergibt

[trish@lillegroenn /tmp]$ ./wb yesterday
 […]
 gestern – yesterday
 only yesterday – erst gestern
 vorgestern – the day before yesterday
 yesterday – gestern
 yesterday – gestern
 […]

zwar ein sortiertes, aber immer noch nicht dublettenfreies Bild. Die Untersuchung der mit ./wb yesterday > /tmp/test in die Datei /tmp/test umgeleitete Ausgabe mit einem Editor ergibt: Der einzige Unterschied zwischen den zwei "yesterday – gestern"-Zeilen sind Whitespace-Zeichen.

Na gut, dann vereinheitlichen wir all diese ('[:blank:]') zunächst zu Leerzeichen (' ') und vereinfachen alle Leerzeichenfolgen mit der tr-Option -s (squeeze") jeweils zu einem einzelnen:

        grep -hwi "$1" $WBDIR/* | tr -s '[:blank:]' ' ' | sort -f | uniq

Und doch – immer noch zeigt sich die Doppelzeile hartnäckig: Natürlich, denn jetzt haben wir in der einen Ausgabe kein und in der anderen genau ein Leerzeichen am Zeilenende, an dem sich uniq stört.

Also holen wir seufzend nochmal sed heraus und ersetzen ein einzelnes Leerzeichen am Zeilenende ($) durch nichts:

        grep -hwi "$1" $WBDIR/* | tr -s '[:blank:]' ' ' | sed -e "s/ $//" | sort -f | uniq

Et voilà – endlich ist das wb-Skript (Listing 1) funktionstüchtig. Jetzt dürfen die Debug-Optionen weg, und root kann es nach /usr/local/bin zur Nutzung für alle kopieren. Da dieses Verzeichnis meistens in der PATH-Variablen enthalten ist, reicht es jetzt auch, wb ohne Pfadangabe aufzurufen.

Listing 1

Das Wörterbuchskript

wb
 #!/bin/bash
 WBDIR=/home/trish/dict
 if [ $# -ne 1 ]; then
         echo "Usage: $0 \"string string …\""
         echo "       $0 string"
         echo "       $0 regexp"
 else
         grep -hwi "$1" $WBDIR/* | tr -s '[:blank:]' ' ' | sed -e "s/ $//" | sort -f | uniq
 fi

Mehrwert

Aufmerksame Leser/innen wundern sich möglicherweise, dass in Listing 1 aus einer vorgestellten plötzlich drei echo-Zeilen wurden. Wer ein wenig mit dem Skript (oder grep) experimentiert hat, weiß, dass man der Shell durch Einschließen mehrerer Strings in Anführungszeichen suggerieren kann, dass sie es trotz allem nur mit einem Argument zu tun hat.

Sobald Benutzer/innen nach einem Ausdruck suchen wollen, der aus mehreren Wörtern besteht, müssen sie den einfach in Gänsefüßchen setzen:

[trish@lillegroenn /tmp]$ wb "sich erinnern" 
 recollect – sich erinnern an
 remember – sich erinnern
 sich erinnern – remember[…]

Diese Benutzungsart sollten wir natürlich dokumentieren:

echo "Usage: $0 \"string string …\""

Damit echo die auszugebenden Hochkommata nicht fälschlicherweise für die Begrenzung des eigenen Arguments hält, müssen sie mit \ escapt (d. h., ihrer Sonderstellung in der Shell beraubt) werden.

Die letzte echo-Zeile

echo "       $0 regexp"

hingegen zielt darauf ab, dass grep von Haus aus nicht nur nach Zeichenketten, sondern auch nach regulären Ausdrücken ("regexps") sucht. Damit kann die Nutzerin zum Beispiel Schreibunsicherheiten elegant überspielen:

[trish@lillegroenn /tmp]$ wb "ye.*y" 
 erst gestern – only yesterday
 Freibauern – yeomanry
 gelbliche – yellowly
 gestern – yesterday
 hefig – yeasty[…]

sucht nach der Übersetzung von Wörtern, die mit ye beginnen und auf y enden. Der Punkt steht dabei für ein beliebiges Zeichen, und der nachgestellte * signalisiert, dass eine beliebige Anzahl (mindestens keins) davon auftauchen soll.

Zu beachten ist hier lediglich, dass auch bei regulären Ausdrücken der Satz gilt: "Manche sind gleicher als andere." Obwohl die Grundregeln gleich sind, sind z. B. nicht alle perl-Regexps auch mit grep zu gebrauchen. Ein Blick in die grep-Manpage ist daher oft von Vorteil …

Glossar

Leserechte

Damit der Inhalt einer Datei mit Hilfe eines Pagers wie less oder eines Editors den Augen eines Users zugänglich gemacht werden kann, muss sie aus Sicht dieses Benutzers das r- ("read"-) Flag tragen. Dies kann mit dem Kommando chmod für die Eigentümerin der Datei (chmod u+r dateiname), die Eigentümergruppe (g+r) und alle anderen (o+r) gesetzt werden. Bei Verzeichnissen lässt das Leserecht die Anzeige des Verzeichnisinhalts mit ls zu. Weitere Rechte sind das Schreib- (w) und das Ausführungsrecht (x). Sie können mit ls -l ("long listing") sichtbar gemacht werden.

Pipe

Die "Rohrleitung", auf der Kommandozeile | geschrieben, nimmt die Standardausgabe des links davon stehenden Kommandos und füttert damit das Kommando auf der rechten Seite.

Shell

Die Kommandozeilenschnittstelle zwischen der Benutzerin und ihren Eingabegeräten und dem Betriebssystem. Die meisten Unix-Shells haben eine mehr oder weniger mächtige Programmiersprache eingebaut.

$

Shells wie die Bourne-Shell (sh), Korn-Shell (ksh) oder die Bourne-Again-Shell (bash, unter Linux auch sh) geben den Inhalt einer Variablen preis, wenn man vor ihren Namen ein Dollarzeichen setzt.

Whitespace

Sammelbegriff für Zeichen, die dem Auge "Hier steht kein Zeichen" vorgaukeln. Darunter fallen z. B. Leer- und Tabulatorzeichen.

Tip a friend    Druckansicht beenden Bookmark and Share
Kommentare