bash und readline für Nicht-mehr-Anfänger

Wenn man mit der bash arbeitet, lernt man sehr bald, solche kleinen Annehmlichkeiten zu schätzen, wie z.B. daß man mit der tab-Taste Dateinamen vervollständigen kann, oder mit der crsr-up-Taste in der history zurückblättern kann. Diese Annehmlichkeiten entstehen aus dem Zusammenspiel zwischen der bash und der readline-Bibliothek von GNU.

Hier sollen nun weitere solche Bonbons vorgestellt werden. Der Text richtet sich nicht an blutige Anfänger, aber auch nicht an Experten. Diese werden das alles schon kennen. Es ist beileibe keine vollständige Liste. Ich habe nur einige Dinge aufgelistet, die ich in letzter Zeit ab und zu hilfreich fand.


Kleine Helferlein: ctrl-a, ctrl-e und ctrl-t

Eine winzige Kleinigkeit, aber praktisch ist sie schon manchmal: Während man eine Befehlszeile editiert, kommt man mit ctrl-a zum Anfang der Zeile, mit ctrl-e zum Ende der Zeile. (Im Deutschen kann man sich dies besonders gut merken: a wie Anfang, und e wie Ende.)

Exakt dasselbe bewirken auch die Tasten Pos-1 und Ende.

Tastnedreher, wie sie bei schnell schreibenden Leuten häufiger vorkommen, korrigiert man am besten mit ctrl-t. Diese Taste vertauscht die Zeichen vor und unter dem Cursor.


Anführungszeichen und Wildcards

Jeder kennt die Benutzung von wildcards wie * und ?. Normalerweise ersetzt die bash solche wildcards durch eine Liste aller passenden Dateinamen, und übergibt diese Liste an das betroffene Programm. Viele Programme können allerdings auch von sich aus solche wildcards auflösen. Und manchmal ist es auch nötig, daß nicht die bash, sondern das Programm diese Auflösung vornimmt. Dies erreicht man, indem man die wildcard in Anführungszeichen setzt.

Beispiele:

   find . -name *.html
   find . -name "*.html"
   find . -name "\"*.html\""
   find . -name \"*.html\"

   ls h*
   ls "h*"

Der find-Befehl gehört zu denjenigen, der wildcards selber auflösen können. Nehmen wir nun an, daß sich der Benutzer in einem Verzeichnis befindet, in dem sich nur eine einzige html-Datei befindet: index.html. Beim ersten Befehl wird die bash nun *.html zu index.html auflösen. Der find-Befehl wird nun alle index.html-Dateien in allen Unterverzeichnissen suchen.

Der zweite Befehl sucht nach allen html-Dateien in allen Unterverzeichnissen. Beim dritten Befehl wird von der bash die Zeichenkette "*.html" (einschließlich Anführungszeichen) an das find-Programm übergeben. Dieses sucht also alle Dateien, auf deren Namen die wildcard "*.html" paßt, die also mit einem Anführungszeichen beginnen, und mit .html" enden. Was der vierte Befehl macht, wird dem Leser als Übung überlassen.

Der ls-Befehl löst wildcards nicht von sich aus auf. Er hat aber die Eigenschaft, daß, wenn man ihm ein Verzeichnis übergibt, er den Inhalt des Verzeichnisses listet. Beim ersten ls-Befehl übergibt die bash eine Liste aller Dateien und Unterverzeichnisse, die mit h anfangen. Das ls-Programm gibt also alle Dateien, die mit h anfangen aus, ebenso wie den Inhalt aller Unterverzeichnisse, die mit h anfangen. (Will man das letztere verhindern, so muß man die Option -d benutzen.)

Der zweite ls-Befehl dagegen schaut, ob es im aktuellen Verzeichnis eine Datei namens h* gibt.


Kommando-Substitution

Schließt man innerhalb eines bash-Kommandos ein anderes Kommando in backquotes (`) ein, so wird dieses durch seine Standard-Ausgabe ersetzt.

Beispiel:

   ls -l `find . -name "*.html"`
   find . -name "*.html" | ls -l

In der ersten Zeile wird der find-Befehl in den backquotes durch eine Liste alles html-Dateien, die find gefunden hat, ersetzt. ls gibt dann die Dateiattribute und die Dateigröße etc. dazu aus.

In der zweiten Zeile wird einfach der Inhalt des aktuellen Verzeichnisses ausgegeben, weil ls die Standardeingabe einfach ignoriert. Der find-Befehl ist hier also ohne jegliche Wirkung.

Häufig kann man den Gebrauch der backquotes durch den Gebrauch des xargs-Kommandos ersetzen. Beide kommen in der Praxis auf nicht besonders häufig vor.

Hier noch ein häufig vorkommendes Beispiel: Das Umbennen mehrerer Dateien. In einem Verzeichnis liegen sagen wir die Dateien urlaub001.JPG bis urlaub528.JPG. Wir möchten die Suffices .JPG umwandeln in Kleinbuchstaben .jpg. Dies geht z.B. durch

   for i in urlaub*; do mv -i $i `echo $i | sed s/JPG/jpg/`; done;

Der echo-Befehl gibt den Namen der Datei aus. Der sed ersetzt den Suffix, und die Kommandosubstitution sorgt dafür, daß sich der mv-Befehl so verhält, als wäre die sed-Ausgabe eines seiner Argumente. Die Option -i sollte man nicht weglassen, damit, wenn etwas schiefgeht, kein Bild überschrieben werden kann. Dieses Beispiel funktioniert natürlich nur auf Dateisystemen, die zwischen Klein- und Großschreibung unterscheiden.


Ein komplizierteres Beispiel mit diversen Anführungszeichen

Man stelle sich vor, daß man alle Einträge in der /var/log/messages von heute auf die Standardausgabe ausgeben lassen will. Die Idee ist, dazu den Befehl grep zu verwenden, und sich das aktuelle Datum mit dem Befehl date zu holen. Die Datumsangaben in /var/log/messages sind von z.B. folgender Form: Apr 5.

   grep "`date +'%b %d'`" /var/log/messages

macht das gewünschte. Wozu aber dienen jetzt die ganzen verschiedenen Anführungszeichen?

  1. Die "double-quotes" werden benötigt, damit z.B. "Apr 5" als eine Zeichenkette aufgefaßt wird. Würde man grep Apr 5 /var/log/messages schreiben, so würde sich grep darüber beschweren, daß es keine Datei namens 5 findet, und nach dem Vorkommen von Apr in /var/log/messages suchen.
  2. Durch die `backquotes` wird erreicht, daß die Ausgabe des date-Befehles an das grep übergeben wird, und nicht der date-Befehl selber.
  3. Durch die 'Apostrophe' schließlich wird erreicht, daß die Zeichenkette, die die Formatangaben für date enthält, als eine Zeichenkette übergeben wird, und nicht als zwei Zeichenketten '%b' und '%d'. Hätten wir den date-Befehl alleine aufgerufen, so hätten hier auch double-quotes genügt. Ohne diese hätte der date-Befehl einen Fehlermeldung "Zu viele Argumente, die keine Optionen sind" ausgegeben.

Wie man aus 3. schließen kann, funktioniert auch der Befehl

   grep "`date +\"%b %d\"`" /var/log/messages

Allerdings gibt es da noch einen kleinen Fallstrick, der dieses Beispiel zu einem eher akademischen macht: date gibt den Monatsnamen ev. nicht in Englisch zurück, sondern in der Sprache des Benutzers. In /var/log/messages stehen aber die Monatskürzel mal in der einen, mal in der anderen Sprache, meist jedoch in Englisch.

In der Praxis braucht man solche Konstrukte eher selten. Und zwar vor allem in bash-Skripts, und kaum bei der Kommando-Eingabe von der Tastatur.


history-Expansion

   !word

führt den letzten eingegebenen Befehl aus, der mit "word" begann.

Beispiel:

   tar -czf /tmp/backup.tgz -X killfile /home/user1 /home/user2 /etc /var /root
      [Fehler: no space left on device]
   df
   cd /tmp
      [Platz schaffen in /tmp, überprüfen der Größe von /home, /etc und /var,
       aufräumen in den home-Verzeichnissen usw..
       .
       .
       .]
   !tar

Hier ist beim Erstellen des Backups ein Fehler aufgetreten. Nachdem die Fehlerursache beseitigt ist, wird der tar-Befehl mit !tar wiederholt. In diesem Fall ist ein Suchen des tar-Befehls mit der crsr-up-Taste zu umständlich, da vielleicht 10 oder 20 Befehle dazwischenliegen. Eine Neueingabe des Befehls ist zu umständlich und fehleranfällig.

Nachteil der history-Expansion ist, daß man ziemlich gut wissen sollte, was man in den letzten 10min oder so gemacht hat. Auch beim Erstellen eines Backups kann man sein System zerstören. Von !rm, !mv oder ähnlichem würde ich im Allgemeinen abraten.


reverse history search

Drückt man während der Befehlseingabe ctrl-r, so wandelt sich der Prompt der bash um in

   (reverse-i-search)`':

Nun kann man eine Zeichenkette eingeben. Die readline-Bibliothek zeigt einem dann den letzten Befehl aus der history, in dem diese Zeichenkette vorkam. Durch Drücken der return-Taste kann man diesen Befehl ausführen, durch drücken von Esc kann man diese Revers-Suche abbrechen und den gefundenen Befehl in die bash-Kommandozeile übernehmen, wo man ihn editieren kann. Am besten probiere man das einfach mal aus.

Ein besonders sinnvolles Beispiel ist mir nicht eingefallen. Tatsächlich kommt es nicht häufig vor, daß man diese Revers-Suche braucht. Von Zeit zu Zeit ist sie dann aber doch ganz praktisch.


Klammer-Expansion

Aus a{b,c,d}e wird abe ace ade. Die bash ersetzt eine Zeichenkette, in der geschweifte Klammern vorkommen, durch eine Liste von Zeichenketten, bei denen die Klammer durch die einzelnen, durch Kommas getrennten Klammerinhalte ersetzt wurden.

Beispiel:

   /bin/rm /tmp/pictures{08,09,10}/*.{gif,jpg}

löscht alle jpg-und gif-Dateien in den Verzeichnissen /tmp/pictures08, /tmp/pictures09 und /tmp/pictures10.

Dieses Feature habe ich nur sehr selten gebraucht. Ein Beispiel aus der Praxis ist folgendes: Ich möchte die Dateien ~/c/archive/libs/lists/lists.c und lists.h in das aktuelle Verzeichnis kopieren. Im Verzeichnis ~/c/archive/libs/lists/ liegen aber noch weitere Dateien lists-1.0.tgz bis lists-1.21.tgz und lists.o. bash-wildcards lassen sich deshalb nicht verwenden. Die zweimalige Angabe des Pfadnamens ist zu lästig. Also habe ich folgenden Befehl benutzt:

   cp ~/c/archive/libs/lists/lists.{c,h} .

Allerdings hätte man die beiden Dateien genausogut einzeln kopieren können. Mit der crsr-up-Taste wären das nur zwei Tastendrücke mehr gewesen.

Die Klammer-Expansion funktioniert nur unter der bash, nicht unter sh.


Dateien, die mit einem Minus-Zeichen beginnen

Will man eine Datei, deren Name mit einem Minus-Zeichen anfängt, sagen wir -f, bearbeiten, so steht man vor dem Problem, daß die meisten Programme solche Namen als Option auffassen. Im günstigsten Fall gibt es die Option -f nicht und das Programm gibt eine Fehlermeldung aus. Im ungünstigsten Fall existiert diese Option und richtet Schaden an.

Mit den verschiedenen Arten von Anführungszeichen ist hier auch nicht viel anzufangen. Denn entweder löst die bash diese bereits auf, oder, wenn die bash sie nicht auflöst, dann löst sie das Programm erst recht nicht auf. Die Lösung des Problems kann also nichts mit der bash zu tun haben.

Es gibt zwei Arten, dieses Problem zu lösen: Die Befehle

   rm ./-f
   rm -- -f

löschen beide die Datei -f. Die erste Möglichkeit ist vorzuziehen, da sie unabhängig von der verwendeten Implementation von (in diesem Fall) rm ist. Viele Programme interpretieren die Zeichenfolge -- als "Jetzt kommen keine Optionen mehr". Deshalb funktioniert in der Regel auch der zweite Befehl.


Weitere Informationen

erhält man wie folgt:

   man bash
   man readline

home     main by Michael Becker, 7/2000. Letzte Änderung: 12/2002.