On a highway to shell ...

Die Linux-Shell richtig ausnutzen

Praxis & Tipps | Praxis

Viele Linuxer arbeiten lieber mit der Tastatur als mit der Maus: Das Baukastensystem der vielen Tools für die Kommandozeile erlaubt es dem Kundigen, mit wenigen Tastendrücken auch komplexe Aufgaben zu meistern.

Aufmacher

Einsteigern mögen sie geradezu höllisch vorkommen, jene so kryptisch erscheinenden Befehle, die versiert wirkende Linuxer auf einer sehr schlicht anmutenden Kommandozeile eingeben. Tatsächlich ist jedoch gar nichts Geheimnisvolles an diesen Systembefehlen oder dem Kommandozeilen-Interpreter, der Shell [[#lit01 1]].

Und auch wenn es heutzutage dank ausgefeilter Desktop-Oberflächen wie KDE oder Gnome möglich ist, auf die Linux-Kommandozeile ganz zu verzichten: Irgendwann kommt der Punkt, wo es sich lohnt, die Geheimnisse der Shell zu erforschen. Und wenn man einmal das Prinzip begriffen hat und die wichtigsten Befehle kennt, hilft einem dieses Wissen auch im Umgang mit anderen Unix-Versionen - trotz der leichten Unterschiede in den Systembefehlen der verschiedenen Unix-Dialekte.

Mit den Unix-Systembefehlen kann man die tausend Alltagsprobleme lösen, die sich früher oder später stellen, wenn man ein Betriebssystem wirklich benutzt und damit arbeitet. Mal eben hundert Dateinamen ändern, in einem Rutsch in allen HTML-Dateien der Dokumentenhierarchie eines Web-Servers die Mailadresse aktualisieren oder feststellen, welcher Benutzer den meisten Plattenplatz verbraucht und welche Konfigurationsdateien man in letzter Zeit editiert hat - all das lässt sich bequem mit kleinen Shellskripten oder sogar in einer einzigen Kommandozeile erledigen.

Der Preis dieser Universalität ist freilich ein gewisser Lernaufwand: Bis man die - auf den ersten Blick reichlich verwirrende - Syntax der wichtigsten Tools und Shell-Konstrukte beherrscht, ist einiges Ausprobieren angesagt. Wichtigste Informationsquelle dabei sind die man-pages - und Tools wie apropos und whatis, die helfen, die gesuchte Information zu finden. Ein Programm wie tkman kann dabei helfen, die Informationsfülle der man-pages zu erschließen. Letztlich ist es doch weniger mühsam, einen Blick in die Dokumentation zu werfen und ein kleines Skript zu schreiben, als immer wieder die gleichen Dinge von Hand zu erledigen.

Unter Linux arbeitet man in aller Regel mit der Standard-Shell bash (bourne again shell), einem Ableger der Bourne-Shell-Familie. Alle folgenden Beispiele beziehen sich auf die bash. Alternative Shells mit eigenen Dialekten, die den meisten Distributionen beiliegen, sind beispielsweise Korn-Shell, C-Shell oder deren Weiterentwicklung tcsh.

Jede Shell hat dabei ihre eigene Art der Konfiguration und eine eigene Syntax, in der man die Skripte programmiert. Möchte man portable Shellskripte schreiben, muss man diese Shell-Dialekte berücksichtigen oder von vornherein auf alle speziellen Features verzichten, die nur eine bestimmte Shell beherrscht.

Shellskripte werden in der Regel in einer eigenen Shell ausgeführt, die durchaus nicht mit der interaktiven Shell übereinstimmen muss, die die Benutzereingaben entgegennimmt. Üblicherweise nimmt man hier /bin/sh, unter Linux meist ein Link auf die bash - oder auch eine andere Bourne-Shell-kompatible Shell.

Die Möglichkeiten der Shell hinsichtlich ihrer Programmierung sind allerdings nur ein Aspekt. Richtig interessant wird es, wenn man die vielen kleinen, aber leistungsfähigen Standard-Unix-Tools mit den Fähigkeiten der Shell kombiniert: `Für jede Aufgabe ein Programm, das diese Aufgabe optimal löst´ - damit stehen dem Anwender eine schier endlose Menge an raffinierten Werkzeugen zur Verfügung, die er untereinander und mit den Möglichkeiten der Shell-Programmierung wie Variablen und Schleifen kombinieren kann.

Viele Aufgaben lassen sich in einer Kommandozeile lösen, andere sind als kleines Shellskript vielleicht etwas übersichtlicher - aber meist haben Sie die Wahl, ob Sie ein Dutzend Systembefehle geschickt kombinieren oder dies in ein Shellskript verpacken. Geht es nur darum, bestimmte Aufrufparameter eines Befehls dauerhaft zu speichern, bietet sich ein Alias an: alias ll=´ls -al´ definiert ein neues Kommando ll als ls -al; nach alias rm=´rm -i´ fragt das System bei jedem Löschen zurück. Shell-Funktionen bieten die Möglichkeit, mehrere Befehle zu kombinieren, ohne gleich ein Skript programmieren zu müssen.

Gerade bei komplexeren Aufgaben bieten Skripte jedoch den Vorteil, dass sie sich leichter testen und korrigieren lassen. Beispielsweise könnte man innerhalb einer Schleife alle Dateien in einem Verzeichnis der Reihe nach bearbeiten, einen neuen Dateinamen erzeugen und die gerade bearbeitete Datei umbenennen. Ersetzt man den Befehl mv, der die Umbenennung erledigt, beim ersten Versuch durch echo, erhält man eine Liste der alten und neuen Dateinamen - und sieht so, ob die Schleife wie gewünscht arbeitet.

tkman
Vergrößern
tkman bändigt die Informationsfülle der man-pages.

Einige wichtige Grundregeln sollte man sich merken: Ein normaler Unix-Befehl nimmt Daten, Dateien oder Eingaben von der Standardeingabe entgegen und gibt das Ergebnis der Aktion auf der Standardausgabe aus. Für Fehlermeldungen steht die Standard-Fehlerausgabe zur Verfügung. Obwohl auch normale Ausgaben wie Fehlermeldungen auf der Konsole landen, lassen sie sich über eine Output-Umleitung trennen: > foo leitet die Standardausgabe in die Datei `foo´ um, 2> bar schreibt die Fehlermeldungen nach `bar´.

Nahezu jeder Befehl akzeptiert Schalter, die das Verhalten des Befehls modifizieren und die durch ein vorgestelltes `-´ gekennzeichnet sind. Ergänzt wird der Befehl durch Parameter und Argumente, die den Schaltern mit übergeben werden, sowie gegebenenfalls die Namen der zu bearbeitenden Dateien. Die meisten Befehle nehmen beliebig viele Dateinamen entgegen. Die Expansion von Wildcards wie `*´ oder `?´, über die sich mehrere Dateien ansprechen lassen, übernimmt übrigens die Shell, nicht der eigentliche Befehl; der erhält lediglich die Liste der passenden Namen: echo * gibt nicht etwa einen Stern, sondern die Namen aller Dateien im aktuellen Verzeichnis aus.

Oftmals enthalten Parameter oder Argumente eines Befehls Sonderzeichen wie `*´ oder `;´ (siehe [#kasten Kasten]). Hier gilt die Regel: Gehört das Sonderzeichen zum Befehl, muss man es durch einfache oder doppelte Anführungszeichen vor der Interpretation durch die Shell schützen. Soll die Shell das Sonderzeichen interpretieren, schützt man es nicht. Andersherum merkt man sich einfach, dass alles, was nicht in die Gruppe der Zeichen a-z, A-Z, Zahlen und _ (Unterstrich) fällt, sicherheitshalber mit Vorsicht zu gebrauchen ist.

Möchte man ein Sonderzeichen als `normales´ Zeichen - etwa in einem Dateinamen - verwenden und ihm seine Sonderbedeutung nehmen, schützt man es mit `\´ vor der Interpretation durch die Shell. Eine ganze Folge von Sonderzeichen lässt sich durch doppelte oder einfache Anführungszeichen schützen, wobei letztere stärker wirken: Innerhalb doppelter Anführungszeichen ersetzt die Shell Variablen durch ihren Wert, innerhalb einfacher Anführungszeichen bleibt der Variablenname erhalten. Wer ganz sichergehen will, dass die Shell seine Befehle wörtlich interpretiert, verwendet einfache Anführungszeichen.

Eine wichtige Rolle bei der Verkettung von Befehlen spielt das Pipe-Zeichen `|´. Zwei Befehle, über eine solche `Pipeline´ verbunden, können miteinander kooperieren: Die Pipe reicht die Ausgabe des ersten Befehls als Eingabe zum zweiten Befehls weiter. Der Effekt ist derselbe, als ob man die Ausgabe des ersten Befehls in eine Datei umleitet und den Inhalt dieser Datei via Input-Umleitung an den zweiten Befehl verfüttert.

ls -al | lpr -Plp1  

etwa druckt den Inhalt eines Verzeichnisses auf dem Drucker `lp1´ aus. Verwirrend daran mag sein, dass man das lpr-Kommando normalerweise in der Form lpr datei.ps verwendet. Aber viele Unix-Kommandos sind so konstruiert, dass sie ihre Eingabedaten entweder aus einer oder mehreren Dateien lesen, deren Name als letzte(s) Argument(e) angegeben werden, oder - bei Fehlen eines Dateinamen - auf Daten von der Standardeingabe warten. Und hier kann dann auch eine Input-Umleitung oder eine Pipe ihre Daten loswerden.

Manchen Befehlen - beispielsweise dem Archivierer tar - muss man auch explizit mitteilen, dass die Daten von der Standardeingabe und nicht aus einer Datei kommen. Hierzu dient der Parameter `-´, der für Standardein- und -ausgabe steht.

Eine wichtige Basis für viele Aktionen ist der Befehl find. Dieses Kommando durchsucht den Verzeichnisbaum nach Dateien und Verzeichnissen, die bestimmte Eigenschaften (aus einer Vielzahl möglicher Kriterien) aufweisen. Standardmäßig listet find die Namen der gefundenen Dateien auf; auf Wunsch wendet das Programm aber auch einen beliebigen Befehl auf alle Fundstücke an. find ist einer der Befehle, bei denen das Artenschutz-Abkommen für Sonderzeichen in Kraft tritt: Es kennt beispielsweise den Parameter `;´ (Ende des mit den gefundenen Dateien auszuführenden Befehls), den man vor der Shell schützen muss.

find / -name "*.conf"  

bedeutet: `Suche in der Verzeichnishierarchie von der Wurzel (/) rekursiv abwärts nach Dateien, die auf .conf enden, und liste sie auf.´ Damit der Wildcard `*´ bei find ankommt, muss man ihn mittels Anführungszeichen vor der Interpretation durch die Shell schützen.

Nun kann man `find´ auch bitten, mit den gefundenen Dateien etwas anderes zu tun:

find /home -name "*~" -exec rm "{}" \;  

bedeutet: `Lösche alle Dateien unterhalb des Verzeichnisses /home, deren Name auf eine Tilde endet´. Auf diese Art wird man die Sicherheitskopien los, die manche Editoren von bearbeiteten Dateien anlegen und die sich dann fröhlich auf der Platte verstreuen.

Der Schalter `-exec´ weist find an, mit den gefundenen Dateien etwas zu tun - nämlich den Befehl rm darauf anzuwenden. Die geschweiften Klammern `{}´ sind in find ein Platzhalter für die Datei, die aktuell bearbeitet, in diesem Fall also von rm gelöscht werden soll. Der Befehl wird mit einem (ebenfalls geschützten) `;´ abgeschlossen, um find mitzuteilen, wann die Argumentliste dieses Befehls endet.

Wem das rekursive Löschen im Verzeichnisbaum zu radikal ist, kann find anweisen, vor jeder Befehlsausführung um Erlaubnis zu bitten:

find / -name "*~" -ok rm "{}" \;   

Über ein anderes Suchkriterium lassen sich mit find leere Dateien oder leere Verzeichnisse löschen:

find / -empty -ok rm "{}" \;   

löscht (mit Nachfrage) ab / abwärts alle leeren Dateien. Um auch leere Verzeichnisse mitzulöschen, ergänzt man rm um den Schalter `-rf´ (für recursive, force). Allerdings darf man zwar leere, aber wichtige Verzeichnisse wie beispielsweise die Spool-Verzeichnisse für Drucker, Mailer und so weiter nicht einfach mitlöschen.

Aber auch dies kann man find mitteilen, indem man ein zweites Suchkriterium mit angibt:

find / \! -regex ".*spool.*"        -empty -ok rm -rf "{}" \;  

`!´ negiert den folgenden Ausdruck: Finde alles, was diesem Kriterium nicht entspricht. Da das Ausrufezeichen ein Sonderzeichen ist, muss man es durch `\´ vor der Interpretation durch die Shell schützen. Möchte man weitere Directorys - etwa die Log-Verzeichnisse - vom Löschen ausnehmen, kann man einfach weitere `\! -regex´-Ausdrücke anhängen.

Über das Ziel hinausschiessen
Vergrößern
Mit `-ok´ kann man sicherstellen, dass find nicht über das Ziel hinausschießt.

Aber was ist eigentlich der genaue Unterschied zwischen `-name´ und `-regex´? Zunächst beziehen sich die Wildcards bei der Option `-name´ nur auf den Dateinamen - die Verzeichnisse in der vollständigen Pfadangabe zu der Datei werden nicht berücksichtigt. Außerdem gelten hier die Regeln des `Globbing´, der Dateinamen-Expansion der Shell. `-regex´ bezieht sich hingegen auf den kompletten Pfad (Verzeichnis- und Dateinamen) und interpretiert das Muster als regulären Ausdruck (`regular expression´) in der Art des `Pattern Matching´ von grep.

`-regex´
Vergrößern
Mit der Option `-regex´ lassen sich gezielt Verzeichnisse vom Löschen ausnehmen.

Die Berücksichtigung der Pfadangabe ist der Grund, warum man `-regex´ verwenden muss, wenn man etwa die Dateien in einem bestimmten Verzeichnis vor find schützen möchte: Da sich `-name´ nur auf den Namen der gerade bearbeiteten Datei bezieht, weiß diese Option nichts davon, in welchem Verzeichnis sich die Datei befindet.

Doch auch die Unterschiede zwischen dem `Globbing´ der Shell und dem Pattern Matching von beispielsweise grep, vi oder sed sind beträchtlich. Ein `.´ bedeutet für die Shell einfach einen Punkt, ein `*´ ersetzt eine beliebige Zeichenfolge beliebiger Länge (auch der Länge null). In regulären Ausdrücken hingegen steht der Punkt für ein beliebiges Zeichen (wie das `?´ auf der Shell); der Stern bedeutet, dass das vorangegangene Zeichen gar nicht bis beliebig häufig vorkommen darf.

Alle Dateien, die auf `.html´ enden, spricht man auf der Shell mit `*.html´ an. Der reguläre Ausdruck lautet `.*\.html´ - ein beliebiges Zeichen beliebig oft, dann ein Punkt (durch `\´ vor seiner Interpretation als Sonderzeichen geschützt), schließlich das `html´. Wirklich alles zu regulären Ausdrücken findet sich in [[#lit02 2]] ; bei etwas bescheideneren Ansprüchen reicht auch die man-page zu grep.

Ein häufiges Problem sind Dateien, die nach einem Transfer von einem DOS- oder Windows-Rechner auf dem Linux-System in Großbuchstaben erscheinen. Eine Datei mit mv umzubenennen ist kein Problem - aber 500 Dateien? Mit einem kleinen Skript ist die Angelegenheit doch etwas fixer erledigt (siehe [#kasten02 Kasten `Groß und klein´]).

In der `for i in ...´-Zeile wird zunächst der in Backticks gesetzte find-Befehl ausgeführt; die Variable i erhält nacheinander die daraus resultierenden Dateinamen zugewiesen. Auch bei dem mv-Befehl wertet das Skript zunächst den Ausdruck zwischen den Backticks aus: Der echo-Befehl schreibt den Inhalt der Variablen $i auf die Standardausgabe; das Pipe-Symbol leitet diese Zeichenkette an tr weiter. Dieser Befehl ersetzt zeichenweise alle großgeschriebenen Buchstaben durch kleingeschriebene und gibt den so geänderten Dateinamen aus, der dann als zweites Argument für mv fungiert.

Übrigens kann man diese mehrzeilige Befehlsfolge auch durchaus an der Kommandozeile eingeben: Drückt man nach dem Eintippen der `for´-Zeile die Return-Taste, bemerkt die Shell, dass der Befehl noch nicht abgeschlossen ist und wartet auf weitere Eingaben - bei der bash durch einen speziellen Prompt (normalerweise `>´) angezeigt.

Über das Ziel hinausschiessen
Vergrößern
Mit dem Prompt > zeigt die Shell, dass sie auf die Fortsetzung eines Befehls wartet.

Vorsichtige Menschen möchten die Sicherheitskopien ihres Editors vielleicht nicht gleich löschen (wie in dem Beispiel oben), sondern lieber in einer Archiv-Datei aufheben:

find / -name "*~" -print | tar cvzf       geloescht.tgz - -remove-files --files-from -  

find erledigt jetzt lediglich die Suche und schreibt die resultierende Dateiliste auf die Standardausgabe. Die Pipe reicht diese Liste von Dateinamen als Standardeingabe an tar weiter. Dieses Archiv-Programm erwartet üblicherweise eine Liste der zu archivierenden Dateien als Argumente beim Aufruf:

tar cvzf archiv.tgz datei1 datei2 ...  

Das `-´ bei der Option `--files-from´ sagt tar jedoch, dass die Dateiliste auf der Standardeingabe zu erwarten ist. Alternativ könnte man bei dieser Option auch eine Datei angeben, die eine Liste mit den Namen aller zu archivierenden Dateien enthält.

Ebenso gut lässt sich der Befehl auch anders formulieren: In

tar cfv konfigs.tgz `find /      -name "*conf" -print`   

liefert der in Backticks stehende find-Befehl eine Liste der passenden Dateien zurück, die tar dann als reguläre Kommandozeilenargumente erhält.

Mit tar lassen sich aber auch komplette Partitionen hin- und herschieben oder kopieren. Tauscht man beispielsweise eine Festplatte aus und möchte dabei sein System von der alten auf die neue Platte übertragen, müssen dabei unbedingt die Dateirechte erhalten bleiben. Der Befehl

cd /mnt/ && (cd / && tar cplf - . ) |      tar xpvf -  

wechselt zunächst in das neue Verzeichnis (`cd /mnt/´) und startet eine Subshell durch Angabe der nächsten Befehle in Klammern. In der Subshell wird der Pfad auf / gesetzt und dort ein tar ausgeführt, das aus dem jetzt aktuellen Verzeichnis alle Dateien aufnimmt und sie nach Standard-Output sichert. Der Schalter `p´ weist tar an, die Dateirechte mit zu sichern; `l´ (für one-file-system) sorgt dafür, dass weder /proc noch das unter /mnt/ eingebundene Ziel-Dateisystem miteinpackt werden. Den Output soll tar in Datei schreiben (Schalter `f´), und zwar in die Standardausgabe (`-´).

In der ursprünglichen Shell (deren Arbeitsverzeichnis /mnt/ ist) nimmt tar die Daten über die Pipe von der Standardeingabe auf und entpackt sie hier - ebenfalls unter Beibehaltung der Dateirechte. Aber warum packt man den Inhalt der alten Platte nicht einfach in ein Archiv, mountet die neue Platte und packt es dort wieder aus? Ganz einfach: Ein solches Archiv bräuchte so viel Plattenplatz, wie Daten hineingepackt werden; die etwas komplizierte Konstruktion des Beispiels kommt ohne Zwischenspeicherung in eine Datei aus.

Trotz ZIP-Drives, Internet-Anbindung und CD-ROMs: Manchmal muss man zum Datentransport doch noch auf die gute alte Diskette zurückgreifen. Leider sind Disketten heutigen Dateigrößen von mehreren Megabyte nicht gewachsen. Mit dem Schalter `M´ (für `multi-volume´) verteilt tar große Dateien automatisch auf mehrere Disketten:

tar cMvf /dev/fd0 *  

sichert alle Dateien im aktuellen Verzeichnis auf so viele Disketten, wie dazu nötig sind; und

tar xMvf /dev/fd0   

packt das Mehrdiskettenarchiv wieder aus.

grep sucht in einer Datei Zeile für Zeile nach einer bestimmten Zeichenkette. Besonders mächtig wird das Tool dadurch, dass es reguläre Ausdrücke beherrscht. Das GNU-grep von Linux entspricht in seinem Funktionsumfang etwa dem egrep (extended grep) anderer Unix-Varianten. Eine simple Suche sieht so aus:

grep "Such das!" datei.txt   

grep durchsucht jetzt Zeile für Zeile von datei.txt nach der Zeichenkette `Such das!´. Mit der Option `-i´ ignoriert grep die Groß-/Kleinschreibung. Standardmäßig gibt das Suchprogramm die Zeilen aus, die den Such-String enthalten; mit der Option `-l´ listet grep lediglich die Namen der Dateien mit Treffern auf. `-n´ ergänzt die Ausgabe der gefundenen Zeilen um die Zeilennummer, mit `-2´ gibt grep jeweils zwei Zeilen vor und nach der Trefferzeile aus. `-v´ sorgt dafür, dass grep die Zeilen ausgibt, in denen das Suchmuster nicht vorkommt.

Beim Einsatz von grep kann man sich weitere Möglichkeiten von regulären Ausdrücken zu Nutze machen: `^´ symbolisiert den Anfang einer Zeile, `$´ das Ende. `^Satz$´ trifft also nur auf Zeilen zu, die lediglich das Wort `Satz´ enthalten.

Option `-n´
Vergrößern
Mit der Option `-n´ aufgerufen, gibt grep die Zeilennummer jeder Fundstelle mit aus.

Interessant wird es, wenn man grep mit find kombiniert. Die folgende Anweisung sucht alle HTML-Dateien, die gestern editiert wurden und die eine Mail-Adresse in Deutschland enthalten; angezeigt werden die entsprechenden Zeilen inklusive Zeilennummer:

find /usr/local/httpd/htdocs/      -name "*html" -ctime 1 -print |      grep -i -n ´mailto:.*\.de´  

Man kann jetzt die Ausgabe noch etwas verschönern, indem man sich mit dem Befehl cut gezielt bestimmte Spalten ausgeben lässt, die durch ein bestimmtes Zeichen - hier ein " - getrennt sind:

find /usr/local/httpd/htdocs/ -name "*html"     -ctime 1 -print | grep -i -n ´mailto:.*\.de´      | cut -d´"´ -f2 | sort | uniq  

zeigt lediglich den Teil der Zeile zwischen dem ersten und zweiten Anführungszeichen - man erhält eine Liste mit den `mailto:irgendwer@irgendwo.de´. sort sortiert diese Liste dann noch nach dem Alphabet, und uniq entfernt alle doppelten Zeilen - jede Mail-Adresse taucht nur noch einmal auf.

sed, der `stream editor´, ist ein vielseitig verwendbares Werkzeug, um eine Datei zeilenweise und automatisiert zu bearbeiten. Mit seds regulären Ausdrücken lässt sich sehr präzise formulieren, was man wie ändern möchte. Ein einfaches sed-Beispiel sieht so aus:

sed -e s/Hund/Katze/g datei.txt > datei.neu  

`Ersetze alle Vorkommen von Hund durch Katze.´ Die geänderten Zeilen schreibt sed auf die Standardausgabe, von wo man sie mit einer Output-Umleitung in eine neue Datei schreiben kann. Im Kasten `Automatisch benachrichtigt´ finden Sie ein Skript, das mit Hilfe von sed eine Liste mit Mail-Adressen so weiterbearbeitet, dass sich an jede dieser Adressen eine Mail verschicken lässt.

sed
Vergrößern
Um die Logik des `Suchens und Ersetzens´ muss man sich bei sed selbst kümmern.

Wie sed bearbeitet auch awk die einzelnen Zeilen einer Datei; dieses Tool ist auf den Umgang mit spaltenweise organisierten Informationen spezialisiert. Angenommen, man will die Gesamtgröße einer partitionierten Festplatte bestimmen. df zeigt die Belegung der Partitionen:

Belegung der Partitionen
Vergrößern

Anstatt jetzt selbst die Zahlen der 1-KByte-Blöcke in der zweiten Spalte zu addieren, überlässt man awk diese Arbeit:

df | awk ´/dev/ {summe += $2} END  { print summe}´  

Auch awk arbeitet mit regulären Ausdrücken: `Wenn die Eingabezeile den regulären Ausdruck /MUSTER/ enthält, mache {irgendeine Aktion}´. Im Beispiel nimmt awk die Zeilen der df-Ausgabe, die den String `/dev/´ enthalten, und addiert die Werte der zweiten Spalte in `summe´ auf. Den mit `END´ gekennzeichneten Anweisungsblock führt awk erst aus, wenn das Ende der Eingaben erreicht ist - im Beispiel wird dann die Summe der Partitionsgrößen ausgegeben. Will man nur den freien Plattenplatz erfahren, addiert man Spalte 4, indem man das `$2´ durch `$4´ ersetzt.

Durch geschickte Kombination der vielen Shell-Programme lassen sich zahllose Aufgaben bei der Bearbeitung von Textdateien, aber auch der Systempflege automatisieren (siehe auch [#kasten03 Kasten `Kleine Helfer´]). Nimmt man dann noch die Programmiermöglichkeiten der Shell - von Variablen über `if´- und `case´-Konstrukte bis zu Schleifen - dazu [[#lit01 1]], lassen sich mächtige Skripte erstellen. (odi)

[1] Oliver Diedrich, Mit flinken Fingern, Die Power der Kommandozeile, c't 12/99, S. 170

[2] Jeffrey Friedl, Reguläre Ausdrücke, O´Reilly 1998

[#anfang Seitenanfang]



Sonderzeichen
. aktuelles Verzeichnis
.. übergeordnetes Verzeichnis
~ Home-Verzeichnis des aktuellen Benutzers
/ trennt die Verzeichnisnamen in Pfadangaben
\ Escape-Zeichen, das folgende Zeichen verliert seine Sonderbedeutung;
am Zeilenende: der Befehl wird in der nächsten Zeile fortgesetzt
:"..." die meisten eingeschlossenen Sonderzeichen verlieren ihre Bedeutung
´...´ alle eingeschlossenen Sonderzeichen verlieren ihre Bedeutung
`...` wird durch das Ergebnis des eingeschlossenen Befehls ersetzt
? steht für einen beliebigen Buchstaben in Dateinamen
* steht für eine beliebige Buchstabenfolge in Dateinamen
[...] steht für einen der Buchstaben innerhalb der Klammern in Dateinamen
! aktiviert die history expansion
{} fasst Kommandos oder Dateinamen zu einer Gruppe zusammen
() fasst Kommandos zu einer Gruppe zusammen, die in einer Subshell ausgeführt werden
< > Eingabe- und Ausgabe-Umleitung
| verbindet Ein- und Ausgabe zweier Befehle (Pipe)
; trennt Befehle innerhalb einer Zeile
& startet einen Befehl im Hintergrund
$ kennzeichnet einen Variablennamen
&& verknüpft zwei Befehle: der zweite wird nur ausgeführt, wenn der erste erfolgreich war
|| verknüpft zwei Befehle: der zweite wird nur ausgeführt, wenn der erste nicht erfolgreich war

[#anfang Seitenanfang]


#!/bin/sh  for i in `find . -name "[A-Z]*.HTM"`; do  	mv $i `echo $i | tr ´A-Z´ ´a-z´`  	echo "Datei $i wurde geaendert!"  done  

[#anfang Seitenanfang]


Ein Problem, vor dem man als Webmaster stehen kann: Man betreut einen ganzen Satz HTML-Seiten eines Kunden, und nun ändert sich dessen E-Mail-Adresse - die auf einer ganzen Reihe von Seiten angegeben ist. Ein Shellskript nimmt einem die Arbeit ab, jetzt sämtliche HTML-Dateien von Hand nach der alten E-Mail-Adresse zu durchsuchen und sie zu ändern.

#!/bin/sh  for DATEI in $*; do 	 	    sed -e "s/\(mailto:\).*\.de/\1neumail\@kunde.de/g" $DATEI > $DATEI.bak    if (grep "neumail@kunde.de" $DATEI.bak &> /dev/null); then      mv $DATEI.bak $DATEI      echo "Ihre Mailanschrift wurde geändert!" | \      mail -s "Betr.: Mailadresse" \      `grep -i ´mailto:.*\.de´ $DATEI | \      cut -d"\"" -f2 | sed -e "s/mailto://"`     else      rm $DATEI.bak    fi  done  

Das Skript lässt sich mit beliebig vielen Dateinamen aufrufen, die es der Reihe nach bearbeitet. Der erste sed-Aufruf ersetzt die Mail-Adressen in `mailto´-Zeilen durch eine neue Adresse (`neumail@kunde.de´); das `\1´ zu Beginn des einzusetzenden Ausdrucks sorgt dafür, dass das `mailto´ - aufgrund der Klammern gespeichert - auch vor der neuen Adresse steht. Die so geänderte Datei schreibt das Skript in eine Sicherheitskopie. Ist in der Sicherheitskopie der Tausch gelungen, überschreibt die Kopie das Original; ansonsten wird die Sicherheitskopie gelöscht.

Der zweite sed-Aufruf dient dazu, das `mailto´ aus den geänderten Mail-Adressen abzuschneiden - genauer gesagt, ersetzt sed in den von grep aufgespürten E-Mail-Adressen das Pattern `mailto´ durch nichts. An die resultierende `nackte´ Mail-Adresse schickt das Skript dann testweise eine Mail.

Um das Skript übersichtlicher zu machen und jedem Befehl eine eigene Zeile zu spendieren, fügt man ans Ende von Befehlen, die innerhalb einer Kette von Anweisungen stehen, einen `\´ ein und beginnt eine neue Zeile. Der Backslash zeigt der Shell an, dass die Befehlskette trotz Zeilenumbruch noch nicht beendet ist und in der nächsten Zeile weitergeht.

[#anfang Seitenanfang]


tail -f zeigt, was in eine Datei geschrieben wird. Mit tail -f /var/log/messages beispielsweise lassen sich die Ausgaben in die LogDatei `online´ verfolgen - etwa beim Laden eines Moduls oder beim Aufbau einer PPP-Verbindung.

watch führt ein Programm periodisch immer wieder aus: watch -n 5 ps ax beispielsweise zeigt alle fünf Sekunden die aktuell laufenden Prozesse.

ps uaxwww: Ärgerlich bei der Anzeige der Prozessliste sind die abgeschnittenen Zeilenenden. Mit ein, zwei oder drei angehängten `w´ wird irgendwann auch der längste Kommandozeilenparameter angezeigt.

Kommentare