Jedes Kommando wird von der Shell einer command expansion unterzogen, bevor es gestartet wird. Dazu wird nacheinander ausgeführt:
Beispiel: Die Kommandozeile
echo {kohl,schäuble}.spende
wird expandiert
zu
echo kohl.spende schäuble.spende
Mehrere solcher Ersetzungen können kombiniert werden.
So wird das
Kommando touch {lesen,schreiben}.{cpp,h}
expandiert
zu touch lesen.cpp lesen.h schreiben.cpp schreiben.h
.
~
) am Anfang eines
Wortes wird durch das home directory des aktuellen Benutzers
ersetzt, wenn kein Name folgt. Ist nach dem ~
dagegen ein Name
angegeben, dann werden die Tilde und der Name durch das
Homeverzeichnis des angegebenen Benutzers ersetzt.
$
) und der nachfolgende Name werden entsprechend der definierten
Variablen ersetzt (siehe dazu Setzen von Variablen, Exportieren).
`
Kommando`
oder
$(
Kommando)
wird
ersetzt durch die Standardausgabe von Kommando.
In der ersten
Form (mit den Gegenapostrophen
`
, auch back quotes)
werden \$
, \`
und \\
ersetzt durch $
, `
und \
.
In der Version der Form $(
Kommando)
erfolgt diese Ersetzung nicht.
In beiden Fällen werden mehrere Zeilen der
Standardausgabe von Kommando zu einer Zeile zusammengefaßt,
indem der Zeilentrenner (line feed, \n
) durch einen
Worttrenner ersetzt wird (das Leerzeichen).
$((
Ausdruck ))
durch seinen Wert
(siehe Numerische Ausdrücke $(( ... ))).
\n
).
*
, ?
,
oder [
enthalten, werden entsprechend den vorhandenen Dateinamen ersetzt (mit
set -f
kann dieses Verhalten abgeschaltet werden, mit
set +f
wieder an).
Wenn eines dieser wild card-Zeichen in einem Wort enthalten ist, dann wird das Wort durch eine alphabetisch sortierte Liste von Dateinamen ersetzt, auf die das angegebene Muster zutrifft (globbering).
Dabei steht ?
für genau ein Zeichen, *
repräsentiert
keines, eines oder beliebig viele Zeichen, und die Folge der Zeichen
von einem [
bis zum nächsten ]
steht für genau ein
Zeichen, das einem der Zeichen zwischen den eckigen Klammern
entsprechen muß. In diesen Klammern dürfen auch Bereiche angegeben
werden (erstes Zeichen-
letztes Zeichen).
Ein Punkt am Anfang eines Namens muß normalerweise explizit angegeben
werden (*
und ?
können zwar generell auch für Punkte
stehen, aber eben nicht am Anfang eines Wortes). Dieses Verhalten kann
aber in der Shell mit dem Befehl shopt -s dotglob
dahingehend
geändert werden, daß alle Datei- und Verzeichnisnamen mit Punkten
(außer .
und ..
) wie andere Namen auch behandelt
werden. Mit shopt -u dotglob
kann man das ursprüngliche
Verhalten wieder herstellen.
Schrägstriche sind von der Ersetzung immer ausgenommen, weil sie nicht zu den Namen gezählt werden.
Beispiele:
*
trifft auf alle Datei- und Verzeichnisnamen (die nicht
mit einem Punkt beginnen üblicherweise) des aktuellen Verzeichnisses zu
/tmp/*
trifft auf alle Dateien und Verzeichnisse in
/tmp
zu
*.*
trifft auf alle Namen zu, die mindestens einen Punkt enthalten
(wichtiger Unterschied zu DOS/Windows!)
urlaub??.txt
trifft auf alle Namen zu, die mit
urlaub
beginnen, dann genau zwei weitere beliebige Zeichen haben, und
auf .txt
enden.
[0-9a-fA-F]*.xy
paßt auf alle Namen, die mit einer
hexadezimalen Ziffer beginnen (also einer Ziffer von 0 bis 9 oder einem Kleinbuchstaben a bis f oder einem
Großbuchstaben A bis F), dann irgendwie weitergehen und
auf .xy
enden.
Für Details (insbesondere, wann und wie Sonderzeichen berücksichtigt oder ignoriert werden): siehe info bash.
Manchmal ist es nötig, die Auswertung eines Kommandos mehrfach
durchführen zu lassen. Hat man beispielsweise in einer
Variablen i eine Zahl, und will anhand dieser Variablen auf den
soundsovielten Positionsparameter zugreifen, hat man ein Problem. Angenommen,
i hat den Wert zwei, und man will dann den Wert von $2
verwenden. Dann liegt es nahe, einen Ausdruck der Art ${$i}
zu
verwenden. Das klappt nur leider nicht, weil die Variablenersetzung
nur ,,einstufig`` durchgeführt wird: $i
wird zwar durch eine 2 ersetzt, aber der daraus resultierende
Teilausdruck
${2}
erzeugt eine Fehlermeldung,
weil zu diesem Zeitpunkt keine weitere
Parameterersetzung mehr durchgeführt wird (die Variablenersetzung fand ja
bereits statt).
Eine Lösung dafür hat man mit dem internen Kommando eval.
eval wird mit einem nachfolgenden Kommando aufgerufen, und läßt das ihm übergebene Kommando ein zweites Mal durch die obige command expansion laufen, führt dann das Kommando aus, und liefert selbst den Rückgabewert des Kommandos zurück.
Diesen Mechanismus kann man auch mehrfach anwenden: Wenn
ein Kommando einmal die Ersetzung durchläuft, dann führt
eval
Kommando zu einer doppelten Ersetzung, und
eval eval
Kommando zu einer dreifachen.
klaus@aw35:~ >
set wer würmer hat ist nie allein
klaus@aw35:~ >
i=2
klaus@aw35:~ >
echo \$$i
$2
klaus@aw35:~ >
echo $$i
783i
klaus@aw35:~ >
echo ${$i}
bash: ${$i}: bad substitution
klaus@aw35:~ >
eval echo \${$i}
würmer
\$$i
) bringt nicht das erhoffte Ergebnis: \$
wird zwar zu einem
einzelnen Dollarzeichen, und das folgende $i
zu einer 2 expandiert. Es fehlt aber daran, das
resultierende $2
nochmals auszuwerten. Stattdessen wird $2
ausgegeben.
Im zweiten Versuch hat man mit $$i
auch nicht mehr Glück: $$
wird durch die aktuelle process id der Shell ersetzt (hier:
783), und mit einem nachfolgenden i ausgegeben.
Der dritte Versuch (${$i}
) bringt einen auch nicht weiter: das
resultierende ${2}
ist nach der Ersetzung von
Parametern ein ungültiger Ausdruck.
Freude kommt erst beim letzten Versuch auf: durch das eval wird
das ganze Kommando zweimal durch die command expansion
geschickt, bevor es ausgeführt wird. Beim ersten Durchlauf läßt
man $i
zu einer 2 expandieren, und beim zweiten Durchlauf mit einem
vorangestellten $
zum Wert des zweiten Positionsparameters (würmer
). Damit dieses
vorangestellte Dollarzeichen aber überhaupt unbeschadet die erste
Ersetzung übersteht, muß man einen back slash (\
)
voranstellen. Beim ersten Ersetzen wird also \$
zu einem
Dollarzeichen, und $i
zu einer 2. Beim zweiten Ersetzen entsteht aus dem dann
vorliegenden ${2}
tatsächlich der Wert der Variablen, nämlich würmer
.
Die Angabe der geschweiften Klammern ist hier übrigens nicht nötig;
das Kommando eval echo \$$i
ist gleichwertig, und funktioniert im Gegensatz zu der Lösung mit
Klammern nicht nur in der bash, sondern auch mit der
Bourne-Shell (/bin/sh).
Ich finde aber die Version mit Klammern verständlicher3.6.
AnyWare@Wachtler.de