Zum Inhalt springen

Metazeichen und Quoting

Schritt 4 der Arbeitsweise: Bevor die Shell ein Programm aufruft, ersetzt sie bestimmte Zeichen in der Befehlszeile. Das Programm sieht nur das Resultat. Wenn du ls *.txt tippst, bekommt ls nie den Stern zu sehen, sondern die fertige Liste a.txt b.txt c.txt. Diese Zeichen mit Sonderbedeutung heissen Metazeichen. Wer sie kennt, kann sie gezielt einsetzen und bei Bedarf gezielt abschalten.

Zeichen Bedeutung
~ Dein Home-Verzeichnis, ~anna das von anna
. Das aktuelle Verzeichnis
.. Das übergeordnete Verzeichnis
- Bei cd -: das vorherige Verzeichnis

~ wird nur am Anfang eines Wortes ersetzt. echo ~/bin gibt /home/du/bin, aber echo "~/bin" gibt ~/bin. Diesen Stolperstein triffst du gleich noch einmal.

Muster Bedeutung Beispiel Passt auf
* beliebig viele Zeichen, auch keines *.conf hosts.conf, .conf
? genau ein Zeichen Radiu? Radius, Radium
[abc] eines der aufgezählten Zeichen tty[123] tty1, tty2, tty3
[a-z] eines aus dem Bereich [a-c]* alles, das mit a, b oder c beginnt
[!a-z] oder [^a-z] keines der Zeichen D[!0-9]L DaL, aber nicht D3L

Die Shell sucht alle Dateinamen, die auf das Muster passen, und setzt sie alphabetisch sortiert an die Stelle des Musters. Zwei Regeln, die oft überraschen:

  • * passt nicht auf Dateien, die mit einem Punkt beginnen. Versteckte Dateien brauchen .* oder .bash*.
  • Passt kein Dateiname, bleibt das Muster unverändert stehen und das Programm bekommt den Stern wörtlich. ls *.xyz meldet dann cannot access '*.xyz'.

Hands-on: echo ist das beste Werkzeug, um zu sehen, was die Shell aus einem Muster macht, bevor du es an rm oder mv übergibst.

Terminal-Fenster
cd /etc
echo *.conf
echo host?
ls -d [a-d]*
ls -d [!a-z]*
ls /dev/tty[0-3]
echo *.xyz

Geschweifte Klammern erzeugen Wortvarianten. Anders als Globs schauen sie nicht ins Dateisystem, sondern produzieren einfach Text:

Terminal-Fenster
echo {a,b,c}
echo datei.{txt,md,pdf}
echo {1..5}
echo {01..10}
echo {a..e}

Das ist praktisch, um Strukturen anzulegen oder Backups zu benennen:

Terminal-Fenster
mkdir -p ~/projekt/{src,docs,tests}
ls ~/projekt
touch ~/projekt/docs/kapitel-{1..3}.md
cp /etc/hosts /tmp/hosts.bak
cp /tmp/hosts{.bak,.kopie}

Die letzte Zeile wird zu cp /tmp/hosts.bak /tmp/hosts.kopie. Ein Alltags-Trick: cp datei.conf{,.bak} erzeugt datei.conf.bak.

Ein Backslash \ nimmt dem nächsten Zeichen seine Sonderbedeutung:

Terminal-Fenster
cd /tmp
touch 'alle*imAll.txt' alle8imAll.txt
ls alle*imAll.txt
ls alle\*imAll.txt
rm alle\*imAll.txt alle8imAll.txt

Der dritte Befehl zeigt beide Dateien, der vierte nur die mit dem wörtlichen Stern. Ein Backslash am Zeilenende hebt den Zeilenumbruch auf, damit kannst du lange Befehle über mehrere Zeilen schreiben.

Statt jedes Zeichen einzeln zu maskieren, setzt du Anführungszeichen. Es gibt drei Varianten mit klaren Regeln:

Art Was die Shell noch ersetzt Typischer Einsatz
'...' einfach nichts, jedes Zeichen bleibt wörtlich Suchmuster für grep, sed, awk, feste Texte
"..." doppelt $ (Variablen, $(...)), ` und \ alles, was Variablen enthält
\ Backslash nur das eine folgende Zeichen bleibt wörtlich einzelnes Sonderzeichen

Innerhalb beider Arten von Anführungszeichen bleiben Leerzeichen, Globs und Klammern erhalten. Der einzige Unterschied ist, ob $ noch arbeitet.

Hands-on:

Terminal-Fenster
echo $HOME
echo "$HOME"
echo '$HOME'
echo \$HOME
echo "Mein Home ist $HOME, *.conf bleibt stehen"
echo 'Preis: 5$ (ohne Variable)'
echo "Anführungszeichen \"innen\" gehen mit Backslash"
echo 'Ein Apostroph geht nicht in '\''einfachen'\'' Anführungszeichen'

Die letzte Zeile ist der bekannte Trick: Ein Apostroph in einfachen Anführungszeichen schreibt man als '\'' (Quoting schliessen, Apostroph maskieren, Quoting öffnen). In der Praxis nimmst du in diesem Fall besser doppelte Anführungszeichen.

$(befehl) wird durch die Ausgabe des Befehls ersetzt. So landet ein Resultat in einer Variable oder mitten in einem Satz:

Terminal-Fenster
echo "Heute ist $(date +%A), es ist $(date +%H:%M) Uhr"
anzahl=$(ls /etc | wc -l)
echo "In /etc liegen $anzahl Einträge"
echo "Kernel: $(uname -r) auf $(hostname)"

Die ältere Schreibweise mit Backticks `befehl` siehst du noch in vielen alten Skripten. Sie funktioniert, lässt sich aber schlecht verschachteln und ist leicht mit einfachen Anführungszeichen zu verwechseln. Schreibe immer $(...).

Jetzt kommt das Zusammenspiel von Schritt 1 (Wörter trennen) und Schritt 4 (ersetzen). Die Shell ersetzt zuerst die Variable und zerlegt das Resultat danach an Leerzeichen in Wörter. Ohne Anführungszeichen wird aus einem Dateinamen mit Leerzeichen plötzlich zwei Argumente.

Hands-on:

Terminal-Fenster
cd /tmp
datei="Mein Bericht.txt"
touch "$datei"
ls -l $datei
ls -l "$datei"

Was erwartest du? Der erste ls sucht zwei Dateien, Mein und Bericht.txt, und findet keine davon. Der zweite findet die richtige. Dasselbe gilt für rm, mv, cp und jedes Skript. Darum lautet die Regel:

Auch bei Schleifen über Dateien zahlt sich das aus. Globs liefern die Namen korrekt, selbst mit Leerzeichen, solange du die Variable in der Schleife wieder quotest:

Terminal-Fenster
touch "Zweiter Bericht.txt"
for f in *.txt; do echo "Datei: $f"; done
rm "Mein Bericht.txt" "Zweiter Bericht.txt"

echo reicht für einfache Meldungen. Sobald Formatierung oder Zeichen wie \n ins Spiel kommen, verhält sich echo je nach Shell unterschiedlich. printf funktioniert überall gleich und kennt Formatangaben wie in C oder Python:

Terminal-Fenster
printf 'Name: %s, Alter: %d\n' "Anna" 17
printf '%-12s %6s\n' "Datei" "Grösse"
printf '%-12s %6d\n' "hosts" 221 "passwd" 2856
printf '%s\n' "$HOME" "$USER"

%s ist ein Text, %d eine ganze Zahl, %-12s ein linksbündiger Text mit zwölf Zeichen Breite. Das Format kommt in einfache Anführungszeichen, die Werte dahinter in doppelte. Hat printf mehr Werte als Platzhalter, wiederholt es das Format, darum gibt die letzte Zeile zwei Zeilen aus.

Lösung anzeigen
Terminal-Fenster
cd /tmp
mkdir -p kurs/modul-0{1..5}
ls kurs
touch kurs/modul-0{1..5}/{notizen,aufgaben}.md
find kurs -type f | wc -l
ls -d kurs/modul-0[24]
echo 'Der Wert von $HOME ist '"$HOME"
# Antwort 4: einfache Anführungszeichen für den festen Teil mit $, doppelte für die Variable.
# Geht auch: echo "Der Wert von \$HOME ist $HOME"
datei="Mein Protokoll.txt"
touch "$datei"
ls -l $datei
ls -l "$datei"
# Antwort 5: Ohne Anführungszeichen zerlegt die Shell den Wert an Leerzeichen in zwei Argumente,
# ls sucht darum "Mein" und "Protokoll.txt" und findet keines von beiden.
printf '%-10s %s\n' "$USER" "$HOME"
rm -r kurs "Mein Protokoll.txt"

Selbstcheck · 6 Fragen

1. Was bekommt ls zu sehen, wenn du `ls *.txt` tippst und a.txt und b.txt existieren?
2. Was gibt `echo '$HOME'` aus?
3. Was wird in doppelten Anführungszeichen noch ersetzt?
4. Welche Dateien erzeugt `touch datei.{txt,md}`?
5. Warum gehört `$datei` in Anführungszeichen?
6. Welche Schreibweise für Kommandosubstitution ist zu bevorzugen?