Bash-Workshop

Aus LinuxUser 08/2000

Bash-Workshop

Die Bash, das unbekannte Wesen

Alle bereits einmal in einer Bash ausgeführten Befehlszeilen werden in einem Speicher – dem History-Buffer – gespeichert. Dieser Teil unserer Serie über die Bash beschäftigt sich mit diesem Speicher und zeigt den Zugriff auf die dort enthaltenen Informationen.

Ein Überblick

Im letzten Teil dieser Serie haben wir verschiedene Möglichkeiten diskutiert, um Befehlszeilen mehr oder weniger unter Zuhilfenahme interner Funktionen der Bash (genauer: der Readline-Library) aus den Informationen zu konstruieren, die der Shell intern zur Verfügung stehen. Eine Zusammenfassung der wichtigsten Tastenkombinationen zeigt Tabelle 1. Besonders die unterschiedlichen Mechanismen der Komplettierung (dem automatischen Ergänzen teilweise eingegebener Zeichenketten) sind sehr nützlich und sparen dem Anwender viele Tastendrücke. Diesmal soll mit der History ein weiteres, effektives Hilfsmittel zur Eingabe und Bearbeitung von Befehlszeilen vorgestellt werden. Die Funktionen für diesen Einsatzzweck sind in der GNU-History-Library (/lib/libhistory.so) zusammengefaßt.

Emacs-Modus

Die Eingabe der Bash kann in zwei unterschiedlichen Stilrichtungen – den Modi – erfolgen. Neben dem voreingestellten Emacs-Modus (der sich an den Gepflogenheiten dieses Editors orientiert) kann auch der VI-Editor imitiert werden. So ist sichergestellt, daß geübte Anwender sehr effektiv mit der Bash arbeiten können und nicht versehentlich falsche Tastenbefehle benutzen, die sie von ihrem Editor gewohnt sind. Die Eingabemodi werden mit dem Befehl

$> set -o vi

in den VI-Modus bzw. durch

$> set -o emacs

zurück in den Emacs-Modus geschaltet.

Tabelle 1:Die wichtigsten Editierfunktionen der Bash im Emacs-Modus

Bewegen des Cursors in der Befehlszeile
[Control-a] Sprung an den Anfang der Befehlszeile
[Control-e] Sprung ans Ende der Befehlszeile
[Control-b] ein Zeichen zurück (nach links)
[Meta-b] ein Wort zurück
[Control-f] ein Zeichen vor
[Meta-f] ein Wort vor
Löschen und “killen” von Teilen der Befehlszeile
[Control-h] Zeichen links löschen
[Control-d] Zeichen unter dem Cursor löschen
[Control-k] Zeile ab Cursorposition killen
[Meta-d] Wort links killen
[Control-u] Zeile killen
[Control-w] Wort links killen
[Meta][Control-h],[Meta][Control-?] Wort links löschen
[Control-x][Control-?] Löschen bis zum Zeilenanfang
Einfügen gekillter Passagen
[Control-y] Einfügen aus dem Kill-Ring
[Meta-y] Kill-Ring rotieren
[Meta-.] ‘ntes Argument der vorigen Zeile einfügen
Andere Funktionen
[Control-g] Aktion abbrechen
[Control-j],[Return] Zeile ausführen
[Control-]] Sprung zum nächsten Auftreten des Buchstabens
[Meta][Control-]] Sprung zum vorherigen Auftreten des Buchstabens
[Control-l] Terminal löschen
[Control-i] komplettieren
[Meta-!] Befehlsnamen komplettieren
[Meta-/] Dateinamen komplettieren
[Meta-@] Hostnamen komplettieren
[Meta-~ ] Usernamen komplettieren
[Meta-$] Variablennamen komplettieren
[Meta-{] Komplettieren in Klammern
[Control-x][Control-v] Shellversion anzeigen
[Control-x][Control-r] Initfile erneut einlesen
[Control-_],  
[Control-x][u] Undo
[Meta][Control-r] Komplettes Undo der Zeile
[Meta-l] Wort in Kleinbuchstaben umwandeln
[Meta-c] Wort mit einem Großbuchstaben beginnen lassen
[Control-t] Vertauschen der Buchstaben um den Cursor
[Meta-t] Vertauschen der Wörter um den Cursor
[Meta-0] bis [Meta-9] Numerische Argumente eingeben
Suchen im Historybuffer
[Control-r] Rückwärtiges Suchen im Historybuffer
[Control-s] Vorwärts im Historybuffer suchen
[Meta][Control-i] Komplettieren aus dem Historybuffer
[Control-<] An den Anfang des Historybuffers
[Control-n] Nächste Historyzeile anzeigen
[Control-p] Vorige Historyzeile anzeigen

Alle Befehlszeilen werden in einer speziellen Datei – der History-Datei – gespeichert. Diese History-Datei stellt damit zum einen eine Art Logbuch dar, anhand dessen Einträge der Anwender sich vergegenwärtigen kann, welche Aktionen bereits ausgeführt wurden; andererseits ermöglicht sie das Lernen aus der Vergangenheit. Eine gute Lösung, die in Form von mehreren Befehlszeilen in der History-Datei vorhanden ist, kann mit wenig Aufwand in ein Shell-Programm (Script) umgesetzt werden.

Die vorhergehenden Befehlszeilen haben übrigens auch für die Konstruktion neuer Zeilen eine besondere Bedeutung. Oftmals sind Eingaben wie die folgenden zu beobachten:

$> ls /usr/share/info
...
bash.info  fileutils.info  libg++.info-4
...
$> cp /usr/share/info/bash.info .
$> emacs /usr/share/info/bash.info
$> info  /usr/share/info/bash.info

In diesen Befehlszeilen entspricht mindestens ein Argument in der aktuellen Befehlszeile einem der vorhergehenden, bzw. bezieht sich darauf. In den meisten Fällen handelt es sich um das letzte Argument der vorhergehenden Befehlszeile. Anwender können sich mit einer ganz einfachen Tastenkombination hier erheblichen Tippaufwand ersparen:

$> ls /usr/share/info
...
$> cp [META][.] /usr/share/info/ba[Tab]sh.info ...

Durch die Tastenkombination [Meta]+[.] fügt der Bashzeileneditor automatisch das letzte Argument der vorherigen Befehlszeile an der aktuellen Position ein. (Nochmals zur Erinnerung: Die Meta-Taste wird auf den normalen Keyboards durch die linke Alt-Taste emuliert oder alternativ durch das Drücken der Esc-Taste vor der Eingabe der zweiten Taste.) Die verwendete Readline-Funktion heißt insert-last-argument. Für Spezialisten: Beim Übergang von der zweiten (cp ...) zur dritten Zeile (emacs...) wird aber nicht das letzte, sondern das vorletzte (das in der Befehlszeile erste) Argument benötigt. Auf dieses kann ganz einfach mit der Funktion yank-nth-arg zugegriffen werden, die an die Tastenkombination [Meta-Control-y] gebunden ist. Die Zählweise der Bash beginnt hierbei mit dem 0ten Argument (dem Befehl) an der ersten Position in der Befehlszeile.

Ein zweiter, sehr nützlicher Mechanismus wird als Quick-Substitution bezeichnet und verwendet ebenfalls die Informationen aus der letzten Befehlszeile. In manchen Situationen ändern sich aufeinanderfolgende Befehlszeichen nur geringfügig:

$> ls ~/test -r
$> rm ~/test -r

In diesem Fall kann die zweite Befehlszeile aus der ersten konstruiert werden, indem ls durch rm ausgetauscht wird. Genau dies ermöglicht die Quick-Substitution:

$> ls ~/test/ -r
$> ^ls^rm[Return]
rm ~/test/ -r
rm: in Verzeichnis <C>test<C> absteigen?

Die Substitution ist nicht auf die Befehle in der Befehlszeile beschränkt:

$> ls ~/test/ -r
$> ^st^st1[Return]
ls ~/test1/ -r
...

Jede beliebige Zeichenfolge kann ausgetauscht werden, allerdings wird nur das erste Auftreten der Zeichen ersetzt.

Die Befehlszeilenhistory (so der korrekte Name) ist zu jedem Zeitpunkt über die Aufwärts-Pfeil-Taste [Cursor hoch] erreichbar. Jede Betätigung dieser Taste holt eine weitere zuvor ausgeführte Befehlszeile zurück. Diese Befehlszeilen können normal editiert werden, die Return-Taste führt die aktuelle Zeile – so wie sie momentan dargestellt wird – aus. Auch das Scrollen in die Gegenrichtung ist möglich: Sind Sie durch allzu heftigen Einsatz der Aufwärts-Pfeil-Taste zu weit in die Geschichte eingetaucht, dann können Sie mit der Abwärts-Pfeil-Taste wieder in Richtung Gegenwart wandern.

Die History-Datei

Die Befehlszeilenhistory wird von der Bash in Form einer Datei gespeichert. Die Bash legt diese Datei im Home-Verzeichnis unter dem Namen .bash_history automatisch an oder, sofern diese Datei bereits existiert, liest sie sie beim Start ein. In dem zweiten Fall wird damit der Inhalt der Datei automatisch in den eingebauten Historybuffer der Bash kopiert. Damit sind die Zeilen über die Cursortasten erreichbar.

Untersuchen Sie einmal, ob sich diese Datei bei Ihnen anfindet. Sie hat etwa folgenden Aufbau:

ps x | grep xemacs
kill -HUB 1325
kill -1 1321
ll -rt
xemacs
bg                
ls -rt
wipe ~/del_me
...

Es handelt sich dabei um eine einfache Textdatei, die mit jedem Editor oder Pager (Seitenanzeigeprogramm à la more und less) angezeigt werden kann. Einige der wichtigsten Funktionen zur Verwaltung der History-Datei ermöglichen das Suchen im Historybuffer.

Suchen im Historybuffer

In vielen Situationen erinnert sich der Anwender, daß er zuvor bereits einmal eine bestimmte Befehlszeile ausgeführt hat, weiß aber oft nicht mehr, wann das war. Und gerade diese Befehlszeile wäre jetzt so nützlich! In diesem Fall hat er die Möglichkeit, den Historybuffer zu durchsuchen. Dazu stellt die Bash ihm mehrere Funkionen zur Verfügung.

Die am meisten verwendete Methode wird als inkrementale Suche bezeichnet. Bei dieser Form der Suche zeigt die Bash immer das Ergebnis automatisch an, ohne daß dazu eine weitere Bestätigung notwendig wäre. Die Tastenkombination [Control-r] aktiviert die dabei eingesetzte Funktion reverse-search-history. Ein Beispiel zeigt den Einsatz dieser Funktion. Würde in der oben gezeigten History nach der Befehlszeile nach der Zeile mit dem Befehl xemacs gesucht werden, so reicht es aus, x einzugeben.

$> [Control-r]
(reverse-i-search)`x': xemacs

Die letzte der beiden kill-Zeilen wird durch Eingabe eines k gefunden. Gäben Sie aber statt dessen ein i ein, landen Sie in der wipe-Zeile. Von hier können Sie durch einen weiteren Buchstaben l weiter rückwärts durch den Historybuffer streifen.

$> [Control-r]
(reverse-i-search)`i': wipe ~/del_me
(reverse-i-search)`il': kill -1 1321

Auf diese Weise wird die Zielzeile durch jedes zusätzliche Zeichen genauer spezifiziert. Sobald die richtige Zeile dargestellt wird, können Sie die Suche mit [ESC] beenden, die angezeigte Befehlszeile mit [Return] ausführen, durch [Control-a] an den Anfang oder mit [Control-e] an deren Ende springen. Wie findet man aber Zeilen, die sich wie die kill-Zeilen nur in wenigen Zeichen voneinander unterscheiden? Hier greift der Vorteil der inkrementalen Suche erst sehr spät. Daher gibt es die Möglichkeit, bei der inkrementalen Suche eine angezeigte Zeile durch erneute Eingabe von [Control-r] zu überspringen, ohne daß die bereits eingegebenen Suchzeichen verloren gehen.

$> [Control-r]
(reverse-i-search)`il': kill -1 1321
[Control-r]
(reverse-i-search)`il': kill -HUB 1325

In die Gegenrichtung (zum Ende des Historybuffers hin) wird die Funktion forward-search-history eingesetzt, die gewöhnlich an die Tastenkombination [Control-s] gebunden ist. Dabei tritt bei vielen Terminals ein Problem auf: durch [Control-s] wird voreinstellungsgemäß das Terminal gesperrt. Mit dem Befehl stty (Set Tele Typewriter) kann diese Voreinstellung aber angezeigt und leicht modifiziert werden:

$> stty -a  
speed 9600 baud; rows 77; columns 80; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undefiniert>;
eol2 = <undefiniert>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk brkint ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff
-iuclc -ixany imaxbel
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt
echoctl echoke   

Die problematische Einstellung wurde fett hervorgehoben. Eine einfache Rekonfiguration reicht aus, um derartige Probleme zu beheben:

$> stty stop ^p  
$> stty stop ^Q  

Die erste Zeile sollte bei allen Terminals funktionieren. Nach dem Ausführen dieser Befehlszeile sollte über [Control-s] die Suchfunktion zur Verfügung stehen (erkennbar an der Ausgabe von (i-search)`': unter der Befehlszeile) und die Terminalausgabe wird nun durch [Control-p] angehalten. Bei einigen Terminals kann mit der zweiten Variante eine Umschaltfunktion durch [Control-q] realisiert werden. Erfolgt eine Terminalausgabe, dann stoppt [Control-q] diese. Ein erneutes Eingeben dieser Tastenkombination startet die Ausgabe wieder. Die entsprechende Befehlszeile sollte beim Aufruf der Bash automatisch ausgeführt werden. In einem der nächsten Teile werden wir dazu verschiedene Möglichkeiten vorstellen. Hier zunächst nur soviel: fügen Sie die Befehlszeile am Ende der Dateien .bashrc,.bash_profile oder .bash_login ein, je nach dem, welche Dateien vorhanden sind.

Die nicht-inkrementalen Suchfunktionen werden bei der Bash selten verwendet. Sie sind daher oft gar nicht an Tasten gebunden. Sie funktionieren wie gewohnt: die Suche beginnt erst nach der Eingabe eines Suchbegriffs. Eine Ausnahme gibt es: Mit der Funktion character-search springt der Cursor zum nächsten Auftreten des Zeichens in der Befehlszeile, auf dem er momentan steht. Diese Funktion ist gewöhnlich an [Control-]] gebunden. In die Gegenrichtung sucht man durch [Meta-Control-]].

Der history-Befehl

Für die Verwaltung des Historybuffers und der History-Datei verfügt die Bash über einen eingebauten Befehl mit dem Namen history. Mit ihm werden alle wichtigen Aktionen ausgelöst bzw. gesteuert. Optionen steuern wie üblich die Funktionalität des Befehls, ohne Optionen und Argument listet er den aktuellen Inhalt des Historybuffers:

$> history … 939  less ~/.bash_history 940  stty -a 941  stty stop ^Q 942  stty stop ^p 943  man readline 944  latex bash-history          …

Folgt dem Befehl ein numerisches Argument (eine Zahl), dann bestimmt diese, wieviele Zeilen ausgegeben werden.

$> history 3   
 942  stty stop ^p
943  man readline
 944  latex bash-history

Weitere Optionen:

  • -a(append): Hängt die seit Sitzungsbeginn neuen Zeilen an die History-Datei an.
  • -c(clear): Durch diese Option wird der Historybuffer gelöscht.
  • -n(new lines): Liest bisher noch nicht eingelesene Zeilen aus der History-Datei in den Historybuffer ein.
  • -r(read history file): Liest die History-Datei erneut in den Historybuffer ein.
  • -w(write history file): Schreibt den Inhalt des Historybuffers in die History-Datei. Diese Datei wird dabei überschrieben. (Bei dieser Form werden keine Zeilennummern erzeugt.) Voreingestellt ist das Speichern in der Datei .bash-history. Folgt der Option aber ein Dateiname, so erfolgt die Ausgabe in die angegebene Datei.
  • -p(perform): Führt eine History-Ersetzung (siehe unten) für die folgende Befehlszeile aus. Das Ergebnis wird nicht in der History-Datei gespeichert.
  • -s(store): Speichert die folgende Befehlszeile als ein Eintrag in der History-Datei. Zuvor wird der letzte Eintrag entfernt.

History-Variablen und -Funktionen

Das grundlegende Verhalten der History-Funktionen wird über eine Reihe von Variablen gesteuert.

  • $HISTSIZE: Die Anzahl der Zeilen, die im Historybuffer gespeichert werden können. Voreingestellt sind 500 Zeilen. Die Eingabe der 501ten Zeile bewirkt, daß die 1te Zeile aus den Historybuffer verschwindet. Um den voreingestellten Zeilenspeicher zu verdoppeln reicht die folgende Befehlszeile aus:
$> export HISTSIZE=1000
  • $HISTFILE: Name und Pfad der Datei zum Speichern des Historybuffers (der History-Datei). Voreingestellt ist die Datei

.bash-historyim Home-Verzeichnis des Anwenders. Die Bash speichert den Inhalt des Historybuffers nicht ab, wenn diese Variable nicht gesetzt wurde.

  • $HISTFILESIZE: Die maximale Anzahl von Zeilen, die in der History-Datei gespeichert werden sollen. Voreingestellt sind 500 Zeilen.
  • $HISTCONTROL: Mit dieser Variablen wird gesteuert, welche Zeilen im Historybuffer aufgenommen werden. Enthält sie das Schlüsselwort ignorespace, dann werden Befehlszeilen, die mit einem Leerzeichen beginnen, ignoriert. Durch ignoredups werden Befehlszeilen nur einmal im Historybuffer gespeichert, auch wenn sie mehrmals hintereinander eingegeben wurden. ignoreboth kombiniert beide Möglichkeiten. Ist die Variable nicht gesetzt, dann zeichnet die History die Befehlszeilen genau so auf, wie sie eingegeben wurden.

Die folgenden Variablen sind nur für Spezialisten interessant:

  • $HISTIGNORE: Diese Variable stellt die wohl weitgehendsten Möglichkeiten zur Verfügung, Zeilen von der Aufnahme in den Historybuffer auszuschließen. In ihr können Ausschlußmuster definiert werden. Zeilen, die diesem Muster entsprechen, werden nicht im Historybuffer gespeichert. Mehrere Muster können durch Doppelpunkte getrennt angegeben werden; jedes Muster muß die gesammte Befehlszeile beschreiben. Normalerweise ist diese Variable ungesetzt.
  • $histchars: In dieser Variablen können bis zu drei Zeichen gespeichert werden, die für die History-Funktion besondere Bedeutung haben: Das erste Zeichen wird zum Einleiten der History-Expandierung verwendet. Voreingestellt ist das Ausrufungszeichen. Das zweite Zeichen dient zur Quick-Substitution. Voreingestellt ist das Hütchen (Caret) ^ . Als drittes kann ein Kommentarzeichen (voreingestellt: #) definiert werden. Alle nach diesem Zeichen auftretenden Zeichen werden in der Befehlszeile ignoriert.

Als letzte ist noch die Variable

  • $HISTCMDzu erwähnen: sie enthält die Nummer der Befehlszeile im Historybuffer.

Numerische Argumente

Der Interpreter für die Tastenfunktionen wertet numerische Argumente aus: Hinter diesem Ausdruck versteckt sich ein interner Zähler, der eine ganze Zahl aufnimmt. Diese Zahl wird dann als Argument für den folgenden Befehl verwendet. Voreingestellt ist natürlich 1, damit fällt dieses Feature normalerweise nicht auf. Die Wirkung zeigt sich erst, wenn eine andere Zahl verwendet wird. Numerische Argumente werden durch die Meta-Taste eingegeben:

[Meta][1] [Meta][2]
(arg: 12)

Bei gleichzeitig gedrückter linker Alt-Taste werden in diesem Beispiel die Ziffern 1 und 2 eingegeben. Der nächste Tastenbefehl wird dadurch 12mal ausgeführt. Nett, oder? Probieren Sie dies einmal in Verbindung mit den History-Funktionen.

Fazit

Die Verwendung der History ist einfach und komfortabel. Anwender sparen sich viel Tipparbeit (und Zeit), indem sie die Informationen aus der letzten Befehlszeile recyclen. Gerade Mechanismen wie die Quick-Substitution oder das Einfügen von Argumenten aus den vorigen Zeilen sollte jeder kennen.

Ein Tip ganz am Rande: Die History zeichnet nur die Befehlszeilen auf. In manchen Situationen ist es aber wünschenswert oder notwendig, auch die Ergebnisse der Aktionen zu protokollieren. Für diesen Einsatz wurde der Befehl script entwickelt. Er wird vor den aufzuzeichnenden Befehlen gestartet. Voreinstellungsgemäß schreibt er alle folgenden Ein- und Ausgaben in der Datei typescript mit. Ihm kann ein anderer Dateiname als optionales Argument übergeben werden. Bestehende Typescript-Files werden gelöscht, wenn nicht die Option -a (append) angegeben wird. Beendet wird script durch [Control-D] oder den eingebauten Shellbefehl exit.

$> scriptScript wurde gestartet, die Datei ist typescript
$> ...
$> exitScript wurde beendet, die Datei ist typescript
$>

In der typescript-Datei selbst finden sich genaue Angaben über die aufgezeichnete Sitzung:

Script wurde gestartet: Wed Jun 21 11:44:45 2000
$>...
$>exitScript beendet: Wed Jun 21 11:46:29 2000

Infos

[1] K. Günther: Kompaktreferenz Linux, MITP 2000

[2] Zilm, Th., Grelck, K.: Linux – die Userreferenz, MITP 1999

LinuxUser 08/2000 KAUFEN
EINZELNE AUSGABE
ABONNEMENTS
TABLET & SMARTPHONE APPS
E-Mail Benachrichtigung
Benachrichtige mich zu:

Hinweis: Dieser Artikel ist älter als ein Jahr, enthaltene Informationen sind möglicherweise veraltet.

0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben