Bash · Quoting · Word Splitting · Globbing · ShellCheck
Quoting in Bash
die häufigsten Fehler und wie man sie vermeidet

Quoting in Bash ist das meistunterschätzte Thema der Shell-Programmierung. Word Splitting und Globbing brechen Skripte auf eine Art, die beim Schreiben nicht sichtbar ist — aber auf Produktionsdaten mit Leerzeichen, Sonderzeichen und Wildcards zuverlässig auftritt. Dieser Artikel deckt alle häufigen Quoting-Fehler auf und zeigt die korrekten Alternativen.

16 Min. Lesezeit Word Splitting · Globbing · IFS · $'...' · SC2086 · Arrays Bash 3.x · 4.x · 5.x · POSIX sh

1. Quoting in Bash — Grundlagen und Expansionsreihenfolge

Quoting in Bash kontrolliert, welche Shell-Expansionen auf einem String durchgeführt werden. Bash verarbeitet jeden Befehl in einer festen Reihenfolge von Expansionen: zuerst Brace Expansion, dann Tilde Expansion, dann Parameter und Variable Expansion, dann Arithmetic Expansion, dann Command Substitution, dann Word Splitting, und schließlich Pathname Expansion (Globbing). Quoting unterbricht diese Kette an bestimmten Punkten: Doppelte Anführungszeichen deaktivieren Word Splitting und Globbing, aber erlauben Parameter-Expansion. Einfache Anführungszeichen deaktivieren alle Expansionen vollständig.

Das Missverständnis, das den meisten Quoting-Fehlern in Bash zugrunde liegt: Variablen werden nicht beim Zuweisen expandiert, sondern beim Verwenden. Das bedeutet, dass eine Variable, die einen Dateinamen mit Leerzeichen enthält, bei der Verwendung ohne Anführungszeichen in zwei oder mehr Wörter aufgeteilt wird — unabhängig davon, wie sie zugewiesen wurde. Quoting in Bash ist nicht optional, sondern fundamentaler Bestandteil der Sprachsemantik. Jede Bash-Variable, die in einem Kontext verwendet wird, in dem Leerzeichen, Tabs oder Sonderzeichen auftreten können, muss in doppelten Anführungszeichen stehen.

ShellCheck kennt den Code SC2086 für nicht gequotete Variablen. Es ist die häufigste ShellCheck-Warnung überhaupt — ein Indikator dafür, wie verbreitet dieser Quoting-Fehler in real existierenden Shell-Skripten ist. Die ShellCheck-Dokumentation zu SC2086 erklärt präzise, in welchen Kontexten fehlende Anführungszeichen zu Problemen führen und wann sie tatsächlich unnötig sind.

2. Word Splitting: der stille Datenvernichter

Word Splitting ist der Mechanismus, durch den Bash eine nicht gequotete Variable in mehrere Wörter aufteilt, wenn sie in Befehlspositionen erscheint. Der Trenner wird durch die Variable $IFS (Internal Field Separator) definiert. Standardmäßig enthält $IFS Leerzeichen, Tab und Newline. Wenn eine Variable einen Dateinamen wie mein dokument.txt enthält und unquotiert verwendet wird, sieht Bash zwei Argumente: mein und dokument.txt. Dieses Verhalten von Quoting in Bash zu ignorieren führt zu Fehlern, die lokal nicht auftreten, weil Testdaten selten Leerzeichen enthalten.

Der klassische Fall: Ein Skript iteriert über Dateien mit for f in $(ls). In einem Verzeichnis ohne Leerzeichen in Dateinamen funktioniert das scheinbar korrekt. In einem Verzeichnis mit my project.tar.gz erhält die Schleife drei Elemente: my, project.tar.gz und behandelt sie als separate Dateien. Das ist der Kern des Word-Splitting-Problems beim Quoting in Bash. Die sichere Alternative ist immer find -print0 mit read -r -d '' f in einem Array oder direkte Glob-Expansion mit Anführungszeichen um die Schleifenvariable.


#!/usr/bin/env bash
set -euo pipefail

# === WORD SPLITTING EXAMPLES ===

# WRONG: SC2086 — unquoted variable, word splitting happens
filename="my project (v2).txt"
ls -la $filename      # Bash sees: ls -la my project (v2).txt — 4 args!
cp $filename /backup/ # Fails or copies wrong files

# RIGHT: quoted variable — treated as single argument
ls -la "$filename"
cp "$filename" /backup/

# WRONG: for f in $(ls) — word splitting + loses exit code
for f in $(ls /var/backups/); do
  echo "$f"  # Splits filenames with spaces
done

# RIGHT: glob expansion — shell handles it natively
for f in /var/backups/*; do
  [[ -f "$f" ]] || continue
  echo "$f"  # Each filename is a single word, properly quoted
done

# RIGHT alternative: null-delimited find for complex filters
while IFS= read -r -d '' f; do
  echo "$f"
done < <(find /var/backups -maxdepth 1 -type f -print0)

# WRONG: IFS manipulation without reset — affects all subsequent word splitting
IFS=":"
read -r user pass uid gid rest < /etc/passwd  # OK here
echo $rest  # SC2086 — but also: IFS is still ":" — side effects!

# RIGHT: local IFS scope for read
while IFS=: read -r user pass uid gid gecos home shell; do
  printf '%s -> %s\n' "$user" "$home"
done < /etc/passwd
# IFS is unchanged outside the while loop

3. Globbing: wenn Variablen zu Wildcards werden

Globbing ist die Expansion von Wildcards wie *, ? und [...] zu passenden Dateinamen. Wie Word Splitting wird Globbing nur auf nicht gequotete Strings angewendet. Das Problem beim Quoting in Bash: Wenn eine Variable einen Wert wie *.txt enthält und unquotiert verwendet wird, expandiert Bash den Wert zu allen passenden Dateinamen im aktuellen Verzeichnis — oder, wenn keine Datei passt, lässt den String entweder unverändert oder gibt einen Fehler aus (abhängig von der nullglob-Option).

Ein subtiler Globbing-Fehler tritt auf, wenn Dateinamen Wildcard-Zeichen enthalten — was selten, aber möglich ist. Ein Dateiname report[2026].pdf enthält ein Bracket-Glob-Muster. Wenn dieser Dateiname unquotiert in einem Befehl erscheint, versucht Bash, ihn als Glob-Muster zu expandieren. Ohne passendes Muster bleibt er unverändert (bei nicht gesetzter nullglob), aber das Verhalten ist undefiniert und kann zu Sicherheitsproblemen führen. Quoting in Bash verhindert Globbing vollständig: In "$filename" sind Wildcards gewöhnliche Zeichen.

4. Doppelte Anführungszeichen: Wann und warum

Doppelte Anführungszeichen sind die wichtigste Quoting-Technik in Bash. Sie deaktivieren Word Splitting und Globbing, erlauben aber weiterhin Parameter-Expansion ($variable), Command Substitution ($(befehl)) und Arithmetic Expansion ($((ausdruck))). Die Daumenregel: Jede Variable, die in einen Befehl eingesetzt wird, gehört in doppelte Anführungszeichen — es sei denn, Word Splitting oder Globbing ist explizit gewünscht. Letzteres ist selten und muss kommentiert werden.

Die häufigste Ausnahme beim Quoting in Bash, bei der doppelte Anführungszeichen tatsächlich unnötig sind: Arithmetische Kontexte wie $(( variable + 1 )) und die rechte Seite von [[ ... =~ regex ]], bei der das Pattern ohne Anführungszeichen als Regex und mit Anführungszeichen als Literal-String behandelt wird. Alle anderen Kontexte — Argument-Listen, Bedingungs-Ausdrücke in [ ] (einfache Klammer), Here-Strings, case-Muster — erfordern Anführungszeichen, wenn die Variable Sonderzeichen enthalten kann.


#!/usr/bin/env bash
set -euo pipefail

# === DOUBLE QUOTE EXAMPLES ===

dir="/path/with spaces/data"
file="report [2026].pdf"

# WRONG: globbing and word splitting in mkdir
mkdir $dir   # SC2086 — tries to create "/path/with" and "spaces/data"

# RIGHT: quoted — creates the full path with spaces
mkdir -p "$dir"

# WRONG: unquoted in [ ] — word splitting causes syntax error or wrong result
if [ -f $file ]; then  # SC2086 — [ sees extra arguments ]
  echo "exists"
fi

# RIGHT: use [[ ]] (no word splitting) or quote in [ ]
if [[ -f "$file" ]]; then
  echo "exists"
fi

# WRONG: command substitution result unquoted
output=$(find . -name "*.log")
for f in $output; do  # SC2086 — word splits the find output!
  echo "$f"
done

# RIGHT: never store find output in a string — use an array
declare -a logs=()
while IFS= read -r -d '' f; do
  logs+=("$f")
done < <(find . -name "*.log" -print0)
for f in "${logs[@]}"; do
  echo "$f"
done

# Regex: pattern must NOT be quoted to be treated as regex
version="2.4.8-p4"
if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
  echo "Valid version format"
fi

# Literal string comparison: quote the pattern
search_term="hello world"
if [[ "$version" == "$search_term" ]]; then
  echo "Match"
fi

5. Einfache Anführungszeichen und $'...' Syntax

Einfache Anführungszeichen sind die stärkste Form des Quoting in Bash: Jedes Zeichen zwischen einfachen Anführungszeichen wird wörtlich behandelt. Keine Variablenexpansion, keine Command Substitution, keine Backslash-Interpretation — nicht einmal ein Backslash kann ein einfaches Anführungszeichen innerhalb einfacher Anführungszeichen escapen. Das macht einfache Anführungszeichen ideal für Strings, die keine Variablenexpansion benötigen und Sonderzeichen enthalten: reguläre Ausdrücke, awk-Programme, sed-Ausdrücke.

Die $'...'-Syntax ist eine Erweiterung des Quoting in Bash, die Escape-Sequenzen versteht, aber keine Variablenexpansion durchführt. $'\n' ist ein Newline-Zeichen, $'\t' ein Tab, $'\e' der Escape-Character (für ANSI-Codes), $'\0' das Null-Byte. Diese Syntax ist besonders nützlich für IFS-Definitionen (IFS=$'\n\t'), ANSI-Farbcodes (RED=$'\e[31m') und Strings mit Steuerzeichen, die sonst schwer darzustellen wären. Der Vorteil gegenüber printf-Expansion: kein Subshell-Fork.

6. Quoting und Arrays: die sichere Alternative

Arrays sind die strukturelle Antwort auf das Word-Splitting-Problem beim Quoting in Bash. Statt mehrere Werte in einem String mit Leerzeichen zu trennen, speichert ein Array jeden Wert als isoliertes Element. Das Array-Builtin in Bash trennt die Konzepte "Liste von Werten" und "String-Repräsentation" klar: Elemente können beliebige Zeichen enthalten, einschließlich Leerzeichen, Tabs und sogar Newlines. Solange das Array korrekt expandiert wird — immer mit "${array[@]}" in doppelten Anführungszeichen und @ statt * — ist jedes Element ein einzelnes sicheres Wort.

Der subtile Unterschied beim Quoting in Bash zwischen @ und * bei Arrays: In doppelten Anführungszeichen expandiert "${array[@]}" zu je einem separat gequoteten Element, während "${array[*]}" alle Elemente mit dem ersten Zeichen von $IFS (standardmäßig Leerzeichen) zu einem einzigen String zusammenführt. ${array[*]} ohne Anführungszeichen verhält sich wie ${array[@]} ohne Anführungszeichen — beide unterliegen Word Splitting. Für Schleifen, Funktionsargumente und Befehlsargumente immer "${array[@]}" verwenden.


#!/usr/bin/env bash
set -euo pipefail

# === ARRAY QUOTING ===

# Simulating filenames with special characters
declare -a files=(
  "normal.txt"
  "file with spaces.log"
  "report [2026].pdf"
  "backup$(date +%Y).tar.gz"  # No expansion in single-quoted assignment!
)
# Correct: single-quoted to prevent expansion during assignment
declare -a files2=(
  'normal.txt'
  'file with spaces.log'
  'report [2026].pdf'
)

# WRONG: ${array[*]} without quotes — word splits on IFS
for f in ${files2[*]}; do   # SC2048/SC2068
  echo "$f"   # "file with spaces.log" becomes 3 loop iterations
done

# RIGHT: "${array[@]}" — each element is one word
for f in "${files2[@]}"; do
  echo "$f"   # "file with spaces.log" is one element
done

# WRONG: passing array as string to function
process() {
  local items="$1"   # Only receives first element or first word!
  for i in $items; do echo "$i"; done
}
process "${files2[*]}"  # SC2048 — string, not array

# RIGHT: pass array elements as separate arguments
process_all() {
  for item in "$@"; do
    echo "Processing: $item"
  done
}
process_all "${files2[@]}"  # Each element is a separate argument

# Array slicing — quoting matters here too
batch=("${files2[@]:0:2}")   # First 2 elements, properly quoted
printf 'Batch element: %s\n' "${batch[@]}"

7. Quoting in Subshells und Command Substitution

Command Substitution mit $(befehl) ist ein häufiger Ort für Quoting-Fehler in Bash. Das Ergebnis einer Command Substitution ist selbst ein String, der — wenn er nicht gequotiert ist — Word Splitting und Globbing unterliegt. files=$(find . -name "*.log") ist kein Array, sondern ein newline-getrennter String. Jede weitere Verwendung ohne Anführungszeichen führt zu Word Splitting auf den Newlines. Das Muster local result=$(some_command) hat zusätzlich das Problem, dass bei einem Fehler in some_command der Exit-Code verloren geht, weil local immer Exit-Code 0 zurückgibt (ShellCheck SC2155).

Innerhalb von doppelten Anführungszeichen können weitere doppelte Anführungszeichen in Command Substitutions verwendet werden: "$(command "argument with spaces")". Das ist korrekt und portable. Die äußeren Anführungszeichen schützen das Ergebnis der Command Substitution vor Word Splitting, die inneren Anführungszeichen sind ein separater Quoting-Kontext für das Argument des inneren Befehls. Dieses verschachtelte Quoting in Bash ist anfangs verwirrend, aber syntaktisch eindeutig definiert.

8. Here Documents und Here Strings

Here Documents (<<EOF ... EOF) sind eine besondere Form des Quoting in Bash. Standard-Here-Documents erlauben Variablenexpansion innerhalb des Blocks — sie verhalten sich wie doppelt gequotete Strings. Um Variablenexpansion zu verhindern, wird das Delimiter-Wort gequotet: <<'EOF' oder <<"EOF" (beide äquivalent für Einzelquoting). Mit <<-EOF werden führende Tabs (nicht Leerzeichen) am Zeilenanfang entfernt, was eingerückte Here Documents in Funktionen ermöglicht.

Here Strings (<<< "$variable") sind eine kompakte Alternative zu echo "$variable" | befehl für Quoting in Bash. Sie vermeiden eine Subshell und sind damit portabler und schneller. Wichtig: Der String in einem Here String unterliegt denselben Quoting-Regeln wie überall: <<< $variable ohne Anführungszeichen führt zu Word Splitting. <<< "$variable" übergib den vollständigen Wert als einen String. Für mehrzeilige Strings mit Variablen ist ein Here Document die sauberere Wahl.

9. Quoting-Fehler im Vergleich

Die folgende Tabelle fasst die häufigsten Quoting in Bash-Fehler zusammen, den jeweiligen ShellCheck-Code und die korrekte Alternative. Sie kann als schnelle Referenz beim Code-Review eingesetzt werden.

Falsches Muster ShellCheck Problem Korrektur
cp $file /dest SC2086 Word Splitting bei Leerzeichen cp "$file" /dest
for f in $(ls) SC2045 Splitting + Globbing in Output for f in ./*
local x=$(cmd) SC2155 Exit-Code von cmd verloren local x; x="$(cmd)"
"${array[*]}" SC2048 Alles ein String, kein Array "${array[@]}"
[ $var = "x" ] SC2086 Syntaxfehler bei Leerzeichen in $var [[ "$var" == "x" ]]

Die häufigsten Quoting-Fehler in Bash lassen sich auf wenige Muster reduzieren: SC2086 (unquotierte Variablen), SC2155 (lokale Deklaration mit Command Substitution) und SC2048 (falsches Array-Expansion-Zeichen). Wer ShellCheck als CI-Gate einsetzt, fängt alle drei automatisch ab. Für Code-Reviews bleibt die manuelle Prüfung von Regex-Kontexten, wo bewusstes Unquoting des Musters erwünscht ist, und von Arrays, die als Argumente übergeben werden.

Mironsoft

Shell-Automatisierung, DevOps-Tooling und Deployment-Infrastruktur

Shell-Skripte ohne versteckte Quoting-Fehler?

Wir analysieren bestehende Bash-Skripte auf Quoting-Fehler, Word-Splitting-Probleme und unsichere Variablenexpansion — und ersetzen fragile Muster durch korrekte, getestete Alternativen.

ShellCheck-Analyse

Vollständiger ShellCheck-Scan mit Priorisierung und Behebungsplan für alle Quoting-Warnungen

Quoting-Refactoring

String-basierte Dateilisten durch sichere Arrays ersetzen, Word-Splitting-Probleme dauerhaft beheben

CI-Integration

ShellCheck als Pflicht-Gate für alle Shell-Änderungen in GitHub Actions oder GitLab CI

10. Zusammenfassung

Quoting in Bash ist nicht optional, sondern die Grundlage sicherer Shell-Skripte. Word Splitting und Globbing brechen Skripte auf eine Weise, die bei der Entwicklung mit einfachen Testdaten nicht sichtbar ist, aber auf Produktionsdaten mit Leerzeichen, Sonderzeichen und Wildcards zuverlässig auftritt. Die drei wichtigsten Regeln: Variablen in doppelten Anführungszeichen ("$variable"), Arrays mit "${array[@]}" expandieren und Command Substitution von lokaler Deklaration trennen (local x; x="$(cmd)").

ShellCheck findet die häufigsten Quoting-Fehler automatisch. Die Integration als CI-Gate stellt sicher, dass neue Skripte nicht mit unquotierten Variablen in die Produktion gelangen. Das manuelle Review bleibt für Kontexte notwendig, in denen bewusstes Unquoting — etwa bei Regex-Mustern in [[...=~...]] — erwünscht ist und korrekt kommentiert werden muss. Mit ShellCheck, der hier beschriebenen Quoting-Systematik und dem konsequenten Einsatz von Arrays statt String-Hacks wird Quoting in Bash zur lösbaren, nicht zur unlösbaren Herausforderung.

Quoting in Bash — Das Wichtigste auf einen Blick

Doppelte Anführungszeichen

Immer um Variablen: "$var". Deaktiviert Word Splitting und Globbing, erlaubt weiter Parameter-Expansion und Command Substitution.

Arrays statt Strings

Dateilisten als Arrays speichern, nie als Strings. Immer "${array[@]}" — niemals "${array[*]}" für Iterationen.

Command Substitution

local x; x="$(cmd)" — trennt Deklaration von Zuweisung, damit Exit-Codes von set -e erfasst werden (SC2155).

ShellCheck

SC2086 (unquotiert), SC2155 (local+command), SC2048 (Array-*). Als CI-Gate integrieren, Warnungen beheben oder begründet unterdrücken.

11. FAQ: Quoting in Bash

1Warum Variablen immer quoten?
Ohne Anführungszeichen führt Bash Word Splitting und Globbing durch. "$variable" behandelt den Wert als einzelnes Argument — unabhängig von Leerzeichen oder Wildcards.
2Einfache vs. doppelte Anführungszeichen?
Doppelt: deaktiviert Splitting/Globbing, erlaubt $expansion. Einfach: deaktiviert alles, wörtlich. $'...' erlaubt Escape-Sequenzen ohne Expansion.
3Was ist $'...' Syntax?
Erlaubt \n, \t, \e im String-Literal ohne Subshell. IFS=$'\n\t', RED=$'\e[31m' — kein $(printf '\033[31m') nötig.
4for f in $(ls) — warum gefährlich?
Word Splitting auf ls-Output: Dateinamen mit Leerzeichen werden aufgeteilt. Wildcards in Dateinamen werden als Glob expandiert. find -print0 + read -d '' ist die sichere Alternative.
5Was ist ShellCheck SC2086?
Nicht gequotete Variable: Word Splitting und Globbing möglich. Die häufigste ShellCheck-Warnung. Korrektur: "$variable".
6ShellCheck SC2155?
local x=$(cmd) — local gibt immer Exit-Code 0. set -e erfasst cmd-Fehler nicht. Korrektur: local x; x="$(cmd)".
7Wann Variable NICHT quoten?
In $(( var + 1 )) und bei [[ $var =~ regex ]] — das Pattern muss unquotiert bleiben, um als Regex zu wirken. Alle anderen Variablen: quoten.
8Apostroph in einfachen Anführungszeichen?
Nicht direkt möglich. Lösung: 'it'\''s' oder $'it\'s' mit $'...' Syntax.
9${array[@]} vs. ${array[*]}?
@ in "...": jedes Element separat gequotet. * in "...": alle Elemente mit IFS-Zeichen zu einem String. Für Schleifen immer "${array[@]}".
10Quoting in Subshells?
Innerhalb von "$()" können weitere doppelte Anführungszeichen stehen: "$(cmd "arg with spaces")". Äußere Quotes schützen das Ergebnis, innere Quotes das Argument.