MWCodebymw.de ↗
Terminal

Warum "$@" in Anführungszeichen gehört – Quoting und Arrays in Bash

Ein Dateiname mit Leerzeichen, und aus einem Argument werden zwei. Das ist keine Kleinigkeit: Genau daraus entstehen Skripte, die die falsche Datei löschen. Die Regeln dafür passen auf eine Seite.

Das Skript funktioniert monatelang. Dann legt jemand einen Ordner namens Alte Rechnungen an, und plötzlich sucht es nach Alte und nach Rechnungen. Der Grund ist immer derselbe: Bash zerlegt unquotierte Variablen an Leerzeichen.

Die Grundregel

datei="Mein Bericht.pdf"

cp $datei /backup/      # cp "Mein" "Bericht.pdf" /backup/   → Fehler
cp "$datei" /backup/    # richtig

Merksatz: Jede Variable in Anführungszeichen, außer du willst die Zerlegung ausdrücklich. Das gilt auch für $(…)-Ergebnisse und für Pfade, die „doch nie Leerzeichen haben".

"$@" gegen "$*" gegen $@

zeige() {
  echo "Anzahl: $#"
  for a in "$@"; do echo "[$a]"; done
}

zeige "Mein Bericht.pdf" "Zweite Datei.txt"
# Anzahl: 2
# [Mein Bericht.pdf]
# [Zweite Datei.txt]
  • "$@" – jedes Argument bleibt ein Argument. Das ist fast immer das Richtige.
  • $@ ohne Anführungszeichen – wird zerlegt, Leerzeichen zerstören die Argumente.
  • "$*" – alle Argumente zu einem String verbunden (mit dem ersten Zeichen aus IFS).

Beim Weiterreichen an einen anderen Befehl:

main() {
  rsync -a --delete "$@" "$ziel"     # alle Argumente unverändert durchreichen
}

Listen gehören in Arrays, nicht in Strings

# Falsch: ein String, der nachher zerlegt werden muss
optionen="--exclude .git --exclude node_modules"
rsync $optionen quelle/ ziel/          # geht zufällig gut, bis ein Pfad ein Leerzeichen hat

# Richtig: ein Array
optionen=(--exclude ".git" --exclude "node_modules" --exclude "Alte Daten")
rsync "${optionen[@]}" quelle/ ziel/

"${array[@]}" expandiert zu genau so vielen Wörtern, wie das Array Einträge hat – unabhängig vom Inhalt. Weitere nützliche Formen:

dateien=(*.csv)                 # Glob direkt in ein Array
echo "${#dateien[@]}"           # Anzahl
echo "${dateien[0]}"            # erster Eintrag
dateien+=("nachzuegler.csv")    # anhängen

Dateinamen einlesen, ohne sie zu zerlegen

# Sicher, auch bei Leerzeichen und Zeilenumbrüchen im Namen:
while IFS= read -r -d '' f; do
  echo "Verarbeite: $f"
done < <(find . -name '*.log' -print0)

IFS= verhindert das Abschneiden von Leerzeichen, -r schützt Backslashes, -d '' trennt an Nullbytes – zusammen mit find -print0 die einzige wirklich sichere Kombination.

Fallstrick

Zwei Dinge, die auch Erfahrene erwischen. Erstens: In [[ … ]] ist Quoting auf der linken Seite unnötig, auf der rechten bei Mustervergleichen sogar bedeutungsverändernd – [[ $a == "$b" ]] vergleicht Text, [[ $a == $b ]] interpretiert $b als Muster. Zweitens: set -u (aus set -euo pipefail) bricht bei leeren Arrays in älteren Bash-Versionen ab; schreib dann "${optionen[@]:-}".

Wenn bei euch Skripte laufen, die niemand mehr anfassen will, schaue ich sie mir gern an: bymw.de.

Quellen

#Bash#Quoting#Arrays#Skripte#Sicherheit

Du brauchst mehr als ein Snippet?

Ich entwickle Android-Apps in Kotlin und moderne Websites für Selbstständige und kleine Unternehmen — von der ersten Idee bis zum Release.

Projekt anfragen →