Bash-Befehle und Bash-Programmierung
© Klaus Gerhardt, 08.2005
(Copyright, Nutzungsbedingungen, Haftungsausschluss, s.u.)
Bei den Wildcards handelt es sich um Jokerzeichen für Dateioperationen
wie Suchen, Kopieren, Verschieben und Löschen. Mit Hilfe der
Wildcards kann man Dateien, die einem bestimmten Muster entsprechen,
zusammenfassen. Nicht verwechseln darf man die Wildcards mit den
Regulären Ausdrücken, wie sie z.B. mit grep verwendet werden. Beide sind
sich recht ähnlich, verfolgen aber andere Zwecke. Wildcards verwendet
man für Dateioperationen. Mit Regulären Ausdrücken sucht man nach
Mustern in Textdateien.
Man unterscheidet in Linux zwischen regulären und versteckten Dateien. Die
versteckten Dateien beginnen mit einem Punkt ".".
Achtung! Auch wenn die Linux-Wildcards grosse Ähnlichkeiten mit den
DOS-Wildcards haben, gibt es gravierende Unterschiede, wie aus den
unten stehenden Beschreibungen zu erstehen ist.
Zu den unten stehenden Wildcards habe ich gleich noch einige erklärende
Beispiele hinzugefügt.
|
Wildcard
|
Beschreibung
|
|
?
|
Ersetzt ein beliebiges Zeichen.
|
|
*
|
Ersetzt beliebig viele oder Null Zeichen und ist gleichzeitig die
Wildcard für alle regulären Dateien.
Die Dateien dürfen nicht mit einem Punkt "." beginnen, allerdings dürfen
sie ab dem 2. Zeichen beliebig viele Punkte enthalten.
|
|
xyz*
|
Beginnt mit xyz, danach beliebig viele Zeichen.
|
|
.*
|
Alle versteckten Dateien, d.h. die Datei beginnt mit einem Punkt.
|
|
{*,.[!.]*}
|
Diese Kombination aus Klammererweiterung und Wildcards berücksichtigt
alle regulären und alle versteckten Dateien und verhindert gleichzeitig,
dass der Vorgang auf höheren Verzeichnisebenen übergreift. Im unten
stehenden Beispiel 2, wird auf diese Problematik noch einmal genauer
eingegangen.
|
|
*.*
|
Die Datei muss mindestens einen Punkt enthalten, kann aber auch mehrere
Punkte enthalten.
|
|
abc*.*
|
Beginnt mit abc, danach mindestens ein Punkt.
|
|
[abc]
|
In den eckigen Klammern stehen Platzhalter für genau ein Zeichen,
hier a, b oder c.
|
|
[o-u]
|
w.v., genau ein Zeichen, in diesem Fall aus dem Bereich der Kleinbuchstaben,
von o-u.
|
|
[A-Z]
|
w.v., hier alle Grossbuchstaben von A-Z. Damit sind dann Kleinbuchstaben,
Zahlen und alle sonstigen Zeichen ausgeschlossen.
|
|
[A-Z]7
|
A7 B7 ... Z7
|
|
[A-Za-z]dat
|
Adat Bdat ... Zdat adat bdat ... zdat
|
|
[!x]
|
Nicht x.
|
|
[^x]
|
Nicht x.
|
|
[!abc]
|
Keines der Zeichen aus dem Zeichenvorrat in der eckigen Klammer.
|
|
[abc]de
|
Kann sein: ade bde cde.
|
|
~
|
Joker für das Home-Verzeichnis.
|
Achtung! Die Ausdrücke mit den Wildcards müssen ggf. in Anführungszeichen
gesetzt werden bzw. bash-eigene Zeichen müssen mit einem Backslash "\"
maskiert werden.
Beispiel 1: In diesem Beispiel werden mit touch und der
Klammererweiterung 3 Dateien angelegt und anschliessend
wird die Ausgabe des Befehls ls mit Hilfe von Wildcards
gefiltert.
/tmp/test # touch {l,m,r}aus ; ls ↵
. .. laus maus raus
/tmp/test # ls [ml]aus ↵
laus maus
/tmp/test # ls [!m]aus ↵
laus raus
Beispiel 2: Hier geht es um die Problematik des ".*" Ausdruckes. Dieser
umfasst nämlich nicht nur ".*" sondern auch "..*". Und dies ist die
Verzeichnisebene des darüberliegenden Verzeichnisses. Im folgenden
Beispiel werde ich dies zuerst mit dem Befehl ls demonstrieren.
Zuerst die Ausgangssituation. Wir haben das Verzeichnis /tmp/test
mit einer versteckten und 3 regulären Dateien, sowie das Unterverzeichnis
uv1 mit 2 versteckten und 2 regulären Dateien.
harry /tmp/test> tree -a ↵
.
|-- .kraus
|-- laus
|-- maus
|-- raus
`-- uv1
|-- .eins
|-- .zwei
|-- drei
`-- vier
Ein Wechsel nach uv1. Ein einfacher ls zeigt die beiden reguären Dateien an:
harry /tmp/test/uv1> ls ↵
drei vier
Ein ls .* führt dann aber zu diesem Ergebnis: Es werden
die beiden versteckten Dateien aus uv1 angezeigt, aber auch
die regulären Dateien aus uv1 da der "." für das aktuelle
Verzeichnis steht und auch noch die Dateien aus dem darüberliegenden
Verzeichnis /tmp/test welches durch ".." repräsentiert wird.
harry /tmp/test/uv1> ls .* ↵
.eins .zwei
.:
drei vier
..:
laus maus raus uv1
Wenn man diese Situation jetzt auf einen Befehl überträgt der
rekursiv arbeitet, also von Verzeichnis zu Vezeichnis weiter
klettert, führt dies zu einem unerwünschten Ergebnis. Im
Falle von rm -r .* werden Dateien aus dem Verzeichnisbaum gelöscht
die gar nicht dafür vorgesehen waren. Im Extremfall der ganze
Verzeichnisbaum. Bei einem cp -R .* ist dies
nicht ganz so dramatisch, aber immer noch ärgerlich.
Deshalb verwendet man diesen Ausdruck:
{*,.[!.]*}
Diese Kombination aus Klammererweiterung und Wildcards berücksichtigt
alle regulären und alle versteckten Dateien und verhindert gleichzeitig,
dass der Vorgang auf höhere Verzeichnisebenen übergreift.
Im Beispiel sieht man, dass dies auch zum gewünschten Ergebnis führt.
harry /tmp/test/uv1> ls {*,.[!.]*} ↵
.eins .zwei drei vier
Auf den Kopierbefehl übertragen, sieht das ganze so aus:
cp -R {*,.[!.]*} /zielverzeichnis
Und hier der Ausdruck noch einmal genau erklärt:
{*,.[!.]*}
Die geschweiften Klammern bewirken eine Klammererweiterung. Die einzelnen
Ausdrücke sind durch Komma voneinander getrennt. D.h. wir haben hier
2 Ausdrücke:
*
und
.[!.]*
Der erste Ausdruck ist klar. Zum zweiten Ausdruck:
Die Datei muss mit einem Punkt beginnen. Das nächste Zeichen darf kein
Punkt sein. Danach folgen beliebig viele oder Null Zeichen.
Weiteres zu Wildcards siehe man 1 bash, dort nach "Pattern Matching" suchen (Fundstelle
bei mir Zeile 1203).
Siehe auch: Klammererweiterung
Zum Seitenanfang
Bei der Klammererweiterung erweitert die bash die Ausdrücke in den
geschweiften Klammern zu mehreren Zeichenfolgen. Vor und hinter
den Klammern stehen optionale Zeichenfolgen, preample bzw. postscript
genannt.
Zur Erklärung ein Beispiel:
touch adresse{1,2,3}
bash erweitert den Ausdruck zu
touch adresse1 adresse2 adresse3
Die Ausdrücke können natürlich auch mehrere Zeichen umfassen:
touch haus{1a,23c,110f}
wird zu
touch haus1a haus23c haus110f
Und Klammererweiterungen können ineinander verschachtelt werden:
/tmp/test # touch adresse{1{a,b},2{x,z},3{1,2}} ; ls ↵
. .. adresse1a adresse1b adresse2x adresse2z adresse31 adresse32
Und auch noch Zeichen hinter der rechten Klammer haben (postscripts):
/tmp/test # touch haus{1,2,3}a ; ls ↵
. .. haus1a haus2a haus3a
Achtung! Wenn man, wie hier im Beispiel unten, das Komma hinter den
letzten Ausdruck schreibt, besteht eine Zeichenfolge nur aus
der preample und dem postscript, sofern vorhanden.
/tmp/test # touch adresse{1,2,3,} ; ls ↵
. .. adresse adresse1 adresse2 adresse3
Weiteres siehe: man 1 bash, dort suchen nach "brace expansion".
Siehe auch:
touch
Zum Seitenanfang
Wenn die bash als Dateimuster eine Wildcard, bzw. Kombinationen von Wildcards
vorfindet, wird aus diesem Muster eine Dateiliste erstellt. Oder anders ausgedrückt
das Muster wird zu einer Dateiliste expandiert. Dieser Vorgang heisst "pathname expansion"
oder "globbing", manchmal auch "file globbing" oder "shell globbing" genannt.
Der deutsche Begriff dafür ist Pfadnamenerweiterung.
Beispiele:
Das Testverzeichnis enthält 3 Dateien, wie hier mit "ls *" zu sehen ist:
klaus@athlon:/tmp/klaus/test> ls *
bar foo foobar
Wenn ich jetzt "echo ls *" ausführe, kann ich das globbing beobachten:
klaus@athlon:/tmp/klaus/test> echo ls *
ls bar foo foobar
Siehe auch: man 1 bash, dort nach "pathname expansion" suchen.
Zum Seitenanfang
Viele bash-Befehle haben die Option -r bzw. -R um rekursiv zu arbeiten. Manche Befehle
arbeiten standard mässig rekursiv (z.B. tar). Rekursiv heisst: der Befehl wirkt nicht
nur auf das aktuelle Verzeichnis und die darin enthaltenen Dateien, sondern auf alle
weiteren Unterverzeichnisse und die darin enthaltenen Dateien. Allerdings ist dies eine
Falle. Denn nur mit der Wildcard "*" funktioniert bei den meisten Befehlen die rekursive
Abarbeitung korrekt. Mit allen anderen Dateimustern nicht. Eine Ausnahme ist der Befehl
find. Diesen muss man deshalb zu Hilfe nehmen, wenn man ein korrektes rekursives Verhalten
erreichen will.
Warum ist dieses Kapitel hier angesiedelt? Weil es mit dem globbing zusammenhängt.
In den folgenden Beispielen wird das rekursive Verhalten mit den Befehlen chmod, grep,
tar und cp dargestellt. Bei dem Befehl chmod erkläre ich dann auch was es mit dem globbing
zu tun hat.
Das ist die Ausgangssituation, dargestellt mit tree und find/ls:
~/test> tree
.
|-- foo
|-- fxx
|-- fxx.html
`-- test.html
|-- bar
|-- bar.html
|-- test2.html
| |-- bar
| `-- bar.html
`-- testx
|-- bar
`-- bar.html
~/test> find * -ls
941615 4 drwxr-xr-x 2 klaus users 4096 Okt 8 11:29 foo
941616 0 -rw-r--r-- 1 klaus users 0 Okt 8 11:52 fxx
941617 0 -rw-r--r-- 1 klaus users 0 Okt 8 11:32 fxx.html
941614 4 drwxr-xr-x 4 klaus users 4096 Okt 8 12:08 test.html
941618 0 -rw-r--r-- 1 klaus users 0 Okt 8 11:33 test.html/bar
941619 0 -rw-r--r-- 1 klaus users 0 Okt 8 11:33 test.html/bar.html
941620 4 drwxr-xr-x 2 klaus users 4096 Okt 8 11:33 test.html/test2.html
941621 0 -rw-r--r-- 1 klaus users 0 Okt 8 11:33 test.html/test2.html/bar
941622 0 -rw-r--r-- 1 klaus users 0 Okt 8 11:33 test.html/test2.html/bar.html
941623 4 drwxr-xr-x 2 klaus users 4096 Okt 8 12:08 test.html/testx
941624 0 -rw-r--r-- 1 klaus users 0 Okt 8 12:08 test.html/testx/bar
941625 0 -rw-r--r-- 1 klaus users 0 Okt 8 12:08 test.html/testx/bar.html
Und das passiert wenn ich dem Befehl chmod echo voranstelle. Man sieht also die Dateiliste
die von der bash aus dem Muster "*", mit Hilfe des globbing, erstellt wird.
~/test> echo chmod -R 777 *.html
chmod -R 777 fxx.html test.html
Und das ist das Ergebnis wenn ich den Befehl ausgeführt habe:
~/test> chmod -R 777 *.html
~/test> find * -ls
941615 4 drwxr-xr-x 2 klaus users 4096 Okt 8 11:29 foo
941616 0 -rw-r--r-- 1 klaus users 0 Okt 8 11:52 fxx
941617 0 -rwxrwxrwx 1 klaus users 0 Okt 8 11:32 fxx.html
941614 4 drwxrwxrwx 4 klaus users 4096 Okt 8 12:08 test.html
941618 0 -rwxrwxrwx 1 klaus users 0 Okt 8 11:33 test.html/bar
941619 0 -rwxrwxrwx 1 klaus users 0 Okt 8 11:33 test.html/bar.html
941620 4 drwxrwxrwx 2 klaus users 4096 Okt 8 11:33 test.html/test2.html
941621 0 -rwxrwxrwx 1 klaus users 0 Okt 8 11:33 test.html/test2.html/bar
941622 0 -rwxrwxrwx 1 klaus users 0 Okt 8 11:33 test.html/test2.html/bar.html
941623 4 drwxrwxrwx 2 klaus users 4096 Okt 8 12:08 test.html/testx
941624 0 -rwxrwxrwx 1 klaus users 0 Okt 8 12:08 test.html/testx/bar
941625 0 -rwxrwxrwx 1 klaus users 0 Okt 8 12:08 test.html/testx/bar.html
Das ist doch ein sehr merkwürdiges Verhalten. Es werden die Zugriffsrechte aller
Dateien und Verzeichnisse, im aktuellen Verzeichnis, auf die das Muster *.html zutrifft
geändert, sowie auch die aller Dateien in den darunterliegenden Verzeichnissen. Hier spielt
das Muster aber keine Rolle mehr. Es werden die Zugriffrechte bei allen
Dateien und darunter liegenden Verzeichnissen geändert. Der Befehl ist in dieser Weise
angewandt also völlig nutzlos.
Der Befehl arbeitet also im aktuellen Verzeichnis korrekt. Ab dem Verzeichnis test.html
arbeitet er dann recursiv, aber ohne das Muster zu beachten. Denn chmod selbst hat mit dem
Dateimuster nichts zu tun. Die Dateien die chmod bearbeiten soll werden durch das globbing
von der Bash zur Verfügung gestellt.
Alternative hierzu: Verwendung von chmod zusammen mit find. Beispiele hierzu findet man im
Projekt 4.
~/test> tree
.
|-- fxx
|-- fxx.html
`-- test.html
|-- bar.html
|-- bar.txt
~/test> grep -r "" *.html
fxx.html:
test.html/bar.html:
test.html/bar.txt:
Obwohl als Dateimuster *.html angegeben ist, findet grep auch das Suchmuster
in test.html/bar.txt.
Auch hier muss man wiederum grep mit find kombinieren. Ein Beispiel hierzu ist
bei dem Befehl
grep beschrieben.
~/test> tree
.
|-- foo
|-- fxx
|-- fxx.html
`-- test.html
|-- bar.html
|-- bar.txt
|-- test2.html
| |-- bar
| `-- bar.html
`-- testx
|-- bar
`-- bar.html
Mit dem Datemuster "*" arbeitet tar korrekt. D.h. alle Dateien werden im Archiv
aufgenommen:
~/test> tar -cvf file1 *
foo/
fxx
fxx.html
test.html/
test.html/bar.html
test.html/test2.html/
test.html/test2.html/bar
test.html/test2.html/bar.html
test.html/testx/
test.html/testx/bar
test.html/testx/bar.html
test.html/bar.txt
Mit dem Muster "*.html" funktioniert auch tar nicht korrekt. Es werden
auch Dateien gepackt die nicht auf das Muster passen.
~/test> tar -cvf file2 *.html
fxx.html
test.html/
test.html/bar.html
test.html/test2.html/
test.html/test2.html/bar
test.html/test2.html/bar.html
test.html/testx/
test.html/testx/bar
test.html/testx/bar.html
test.html/bar.txt
Auch hier kommt wieder der Befehl find zu Anwendung um ein einwandfreies rekursives
Verhalten zu erreichen. Ein Beispiel ist bei dem Befehl
tar beschrieben.
Zum Abschluss noch ein Beispiel mit dem Befehl cp. Aus dem unten stehenden Verzeichnisbaum sollen
alle Dateien mit dem Muster "*.html" kopiert werden.
~/test> tree
.
|-- fxx
|-- fxx.html
`-- test.html
|-- bar.html
|-- bar.txt
|-- test2.html
| |-- bar
| `-- bar.html
`-- testx
|-- bar
`-- bar.html
Der Kopierbefehl:
~/test> cp -r *.html /tmp/klaus/test
Das Ergebnis:
klaus@athlon:/tmp/klaus/test>tree
.
|-- fxx.html
`-- test.html
|-- bar.html
|-- bar.txt
|-- test2.html
| |-- bar
| `-- bar.html
`-- testx
|-- bar
`-- bar.html
Auch hier wurden wieder Dateien kopiert die dem Muster nicht entsprechen.
Fazit: Bei den meisten Befehlen funktioniert die Rekursion nur mit dem
Dateimuster "*". Wenn man mit einem anderen Muster arbeitet muss man den
Befehl find zur Hilfe nehmen. Denn dieser arbeitet von Natur aus "echt"
rekursiv.
Siehe auch: find.
Zum Seitenanfang
© Klaus Gerhardt • http://linuxseiten.kg-it.de
-
Dieses Dokument darf in unveränderter Form vervielfältigt und weitergereicht werden,
sofern diese Nutzung privat erfolgt.
-
Der Name des Autors muss genannt werden, sowie die in diesem Absatz festgelegten Nutzungsbedingungen.
-
Eine kommerzielle Nutzung ist nur mit der Zustimmung des Autors erlaubt.
-
Die Vervielfältigung entsprechend den o.g. Bedingungen ist sowohl in elektronischer Form,
als auch auf Papier zulässig.
Der Autor haftet weder für die Anwendung, der in diesem Dokument
beschriebenen Verfahren, noch für die Anwendung von beigefügten Shell-Skripten.
Die Haftung liegt alleine beim Anwender.
Shellskripte, die mit einem Link von diesem Dokument aus heruntergeladen werden
können, bilden einen Teil dieses Dokumentes und müssen unverändert zusammen mit diesem Dokument
weitergereicht werden. Man kann diese Shellskripte jedoch auch separat verwenden. Dann darf man
sie nach belieben verändern. |