declare-Flags in Bash: -i, -x, -A, -n im Detail verstehen
AI generated
$_
#!/
Bash · Variablen · Scripting
declare-Flags in Bash
-i, -x, -A, -n und -r im Detail verstehen

declare ist in Bash weit mehr als eine sichtbare Deklaration. Jedes Flag, ob Integer-Erzwingung, Export, assoziatives Array, Namensreferenz oder Readonly, verändert das tatsächliche Verhalten einer Variable bei jeder Zuweisung, mit Fallstricken, die sich erst im echten Skriptlauf zeigen.

15 Min. Lesezeit declare -i · -x · -A · -n · -r Bash 4.x · 5.x

1. Warum declare-Flags mehr sind als reine Kosmetik

declare ist in Bash weit mehr als eine Möglichkeit, eine Variable sichtbar als solche zu deklarieren. Jedes der optionalen Flags, etwa -i, -x, -A, -n oder -r, verändert das tatsächliche Verhalten der Variable bei jeder folgenden Zuweisung und jedem Lesezugriff, nicht nur ihre Darstellung in declare -p.

Wer diese Flags nicht kennt, verzichtet auf eingebaute Fehlerquellen-Vermeidung, etwa automatische Integer-Prüfung, und schreibt stattdessen manuelle Validierungslogik, die Bash eigentlich schon eingebaut mitbringt. Der folgende Abschnitt geht jedes wichtige Flag einzeln durch, zeigt sein tatsächliches Verhalten mit einem Beispiel und benennt die Fallstricke, die in echten Skripten regelmäßig auftreten.

2. declare -i: Integer-Erzwingung und ihre Nebenwirkungen bei Arithmetik

Eine mit declare -i angelegte Variable wertet jede Zuweisung automatisch als arithmetischen Ausdruck aus, nicht als Zeichenkette. Eine Zuweisung wie count="2 + 3" ergibt bei einer normalen Variable die Zeichenkette "2 + 3", bei einer mit -i deklarierten Variable dagegen die Zahl 5, weil Bash den rechten Ausdruck durch denselben Mechanismus wie in einer arithmetischen Erweiterung auswertet.

Die Nebenwirkung, die viele überrascht: Eine Zuweisung eines nicht-numerischen Strings an eine -i-Variable führt nicht zu einem Fehler, sondern stillschweigend zu 0. Wer also Benutzereingaben in eine -i-Variable schreibt, sollte vorher mit einem regulären Ausdruck prüfen, ob es sich tatsächlich um eine Zahl handelt, statt sich auf eine Fehlermeldung von Bash zu verlassen.


declare -i count
count="2 + 3"
echo "$count"        # 5

count="abc"
echo "$count"        # 0, kein Fehler!

if [[ "$1" =~ ^-?[0-9]+$ ]]; then
  declare -i n="$1"
else
  echo "Ungueltige Zahl: $1" >&2
  exit 1
fi

3. declare -x: Export ins Environment und Sichtbarkeit für Subshells

declare -x markiert eine Variable zum Export, identisch zu export VAR=wert, mit dem Unterschied, dass declare -x auch problemlos mit anderen Flags kombinierbar ist. Eine exportierte Variable landet im Environment jedes Kindprozesses, den das Skript ab diesem Zeitpunkt startet, egal ob es sich um ein anderes Bash-Skript, ein Python-Programm oder ein beliebiges externes Kommando handelt.

Ein häufiger Fallstrick ist die Annahme, Export würde auch in die andere Richtung wirken, also Variablen aus einem Kindprozess zurück in das aufrufende Skript spiegeln. Das ist nicht der Fall, die Vererbung von Umgebungsvariablen läuft ausschließlich von Eltern- zu Kindprozess. Wer Daten aus einem Kindprozess zurückbekommen will, braucht Command Substitution oder eine Datei, niemals eine gemeinsame Umgebungsvariable.


declare -x DEPLOY_ENV="production"

check_env() {
  # Runs in a subshell/child process, sees DEPLOY_ENV automatically
  bash -c 'echo "Child sees: $DEPLOY_ENV"'
}
check_env
# Child sees: production

4. declare -A im Detail: der Unterschied zu einfachen indizierten Arrays

declare -A legt ein assoziatives Array mit Zeichenketten-Schlüsseln an, im Unterschied zu declare -a, das ein indiziertes Array mit fortlaufenden numerischen Indizes erzeugt. Der entscheidende Unterschied zeigt sich beim Zugriff: array[foo] sucht bei einem assoziativen Array nach dem Schlüssel "foo", bei einem indizierten Array wird "foo" dagegen als arithmetischer Ausdruck ausgewertet, was bei einer nicht-numerischen Zeichenkette zu 0 führt.

Ein assoziatives Array muss zwingend vor der ersten Zuweisung mit declare -A deklariert werden, sonst legt Bash automatisch ein indiziertes Array an, und jede spätere Zuweisung mit einem Zeichenketten-Schlüssel schlägt fehl oder erzeugt unerwartetes Verhalten. Diese Reihenfolge, erst declare -A, dann Zuweisungen, ist deshalb keine Stilfrage, sondern zwingend erforderlich.


declare -A user
user[name]="Alice"
user[role]="admin"

declare -a list
list[foo]="wert"   # "foo" wird zu 0 ausgewertet!
echo "${list[0]}"  # wert

5. declare -n: Namensreferenzen als Pass-by-Reference-Mechanismus

declare -n erzeugt eine Namensreferenz, umgangssprachlich Nameref: Die deklarierte Variable verhält sich wie ein Alias auf eine andere, per Namen angegebene Variable. Lesezugriffe und Zuweisungen auf den Nameref wirken direkt auf die referenzierte Variable, was Bash die einzige eingebaute Möglichkeit gibt, Variablen per Referenz statt per Kopie an Funktionen zu übergeben.

Die größte Fallgrube bei Namerefs ist ein Namenskonflikt: Wählt eine Funktion für ihren lokalen Nameref denselben Namen wie eine Variable, die der Aufrufer übergibt, entsteht eine Selbstreferenz und Bash bricht mit einem Fehler ab. Deshalb ist es guter Stil, Nameref-Namen in Funktionen konsequent mit einem eindeutigen Präfix zu versehen, das im Rest des Skripts nicht vorkommt.


set_value() {
  local -n ref="$1"
  ref="$2"
}

my_var="alt"
set_value my_var "neu"
echo "$my_var"   # neu

6. declare -r: Readonly und wann Variablen unveränderlich sein sollten

declare -r markiert eine Variable als schreibgeschützt, identisch zu readonly VAR=wert. Jeder spätere Versuch, den Wert zu ändern, führt zu einem Fehler und, ohne set -e, zu einer Fehlermeldung auf stderr, während das Skript weiterläuft, was leicht übersehen wird, wenn Rückgabewerte nicht konsequent geprüft werden.

Readonly eignet sich für alles, was sich nach der Initialisierung nicht mehr ändern soll: Pfade, Konstanten, per Kommandozeile übergebene Konfigurationswerte. Der Schutz wirkt für die gesamte verbleibende Laufzeit des Skripts und kann nicht rückgängig gemacht werden, auch nicht mit einem erneuten declare, was Readonly zum richtigen Werkzeug macht, wenn versehentliche Überschreibung ein reales Risiko ist.

7. Flags kombinieren: -ir, -ax und typische Fallstricke bei gleichzeitiger Nutzung

Mehrere Flags lassen sich in einem einzigen declare-Aufruf kombinieren, etwa declare -ir für eine schreibgeschützte Ganzzahl-Konstante oder declare -ax für eine exportierte Integer-Variable. Die Reihenfolge der Buchstaben spielt dabei keine Rolle, declare -ri und declare -ir verhalten sich identisch, weil Bash die Flags unabhängig voneinander als Bitmaske auf die Variable anwendet.

Ein typischer Fallstrick entsteht, wenn -r zu früh gesetzt wird: declare -ir MAX=10 gefolgt von einem späteren Versuch, MAX für einen abweichenden Lauf neu zu berechnen, schlägt fehl, weil Readonly bereits aktiv ist. In Skripten, die Konstanten abhängig von Kommandozeilenargumenten berechnen, sollte -r deshalb erst gesetzt werden, nachdem der endgültige Wert feststeht, nicht schon bei der ersten Deklaration.


declare -ir MAX_RETRIES=5
declare -ax WORKER_COUNT=4

echo "$MAX_RETRIES / $WORKER_COUNT"

MAX_RETRIES=10   # Fehler: readonly variable

8. Scope: declare in Funktionen, lokale Variablen und das -g-Flag

Innerhalb einer Funktion aufgerufen, macht declare eine Variable standardmäßig lokal zur Funktion, exakt wie local, mit der einzigen Ausnahme, dass sich declare zusätzlich mit -i, -A, -n oder -x kombinieren lässt. Nach Verlassen der Funktion existiert eine solche Variable nicht mehr, unabhängig davon, ob eine gleichnamige globale Variable existiert.

Wer innerhalb einer Funktion bewusst eine globale Variable anlegen oder überschreiben will, statt eine lokale Kopie zu erzeugen, braucht das Flag -g: declare -g -A cache. Ohne -g würde derselbe Aufruf innerhalb einer Funktion eine neue, funktionslokale Variable erzeugen, die die eigentlich gewünschte globale Variable verdeckt, ein Fehler, der oft erst beim zweiten Funktionsaufruf auffällt, wenn der erwartete Zustand plötzlich verschwunden ist.

9. Typische Fehler beim Umgang mit declare-Flags und wie man sie findet

Die häufigsten Fehler mit declare-Flags entstehen nicht durch falsche Syntax, sondern durch falsche Annahmen über das Verhalten: dass -i Fehler bei ungültigen Zahlen wirft, dass declare -A automatisch für spätere Zuweisungen erkannt wird, oder dass ein Nameref und die referenzierte Variable unabhängig voneinander verändert werden können. Alle drei Annahmen sind falsch und führen zu Fehlern, die sich erst spät im Skriptlauf zeigen.

ShellCheck erkennt viele dieser Fallstricke bereits statisch, etwa fehlende declare -A vor einer assoziativen Array-Nutzung, findet aber nicht jeden Laufzeitfehler wie die stille 0-Konvertierung bei -i. Wer declare-Flags produktiv einsetzt, sollte deshalb zusätzlich zu ShellCheck eigene Tests schreiben, die genau die hier gezeigten Randfälle abdecken: ungültige Zahlen, Namenskonflikte bei Namerefs und vergessenes -g in Funktionen.

Flag Bedeutung Typischer Fallstrick Kombinierbar mit
-i Integer-Erzwingung, arithmetische Auswertung Ungültige Werte werden still zu 0, kein Fehler -r, -x, -g
-x Export ins Environment für Kindprozesse Wirkt nur Eltern zu Kind, nie zurück -i, -r, -g
-A Assoziatives Array mit String-Schlüsseln Muss vor der ersten Zuweisung deklariert werden -r, -g
-n Namensreferenz, Pass-by-Reference Namenskonflikt mit Aufrufer-Variable bricht ab lokal in Funktionen üblich
-r Readonly, schreibgeschützt für die Laufzeit Zu früh gesetzt blockiert spätere Neuberechnung -i, -x, -A

Mironsoft

Shell-Automatisierung, DevOps-Tooling und Deployment-Infrastruktur

Shell-Skripte, die in der Produktion zuverlässig laufen?

Wir analysieren bestehende Bash-Skripte, erkennen fragile Muster und ersetzen sie durch robuste Bash-Patterns: mit vollständiger Fehlerbehandlung, Logging und sicherer Parallelisierung für euren Deployment-Stack.

Code-Review

ShellCheck-Analyse und manuelle Prüfung auf kritische Bash-Pattern-Verstöße.

Refactoring

Fehlerbehandlung, Logging und sichere Dateioperationen nachrüsten.

CI-Integration

ShellCheck und BATS in Pipelines integrieren und Regressionstests aufbauen.

10. Zusammenfassung

declare-Flags in Bash: Das Wichtigste auf einen Blick

-i Integer

Erzwingt arithmetische Auswertung bei jeder Zuweisung, ungültige Werte werden ohne Fehlermeldung zu 0.

-x Export

Macht eine Variable im Environment aller Kindprozesse sichtbar, wirkt aber nur von Eltern zu Kind.

-n Nameref

Erlaubt Pass-by-Reference für Funktionen, Namenskonflikte mit der Zielvariable müssen vermieden werden.

-g für globalen Scope

Innerhalb von Funktionen nötig, um bewusst eine globale statt eine lokale Variable anzulegen.

11. FAQ: declare-Flags in Bash: Das Wichtigste auf einen Blick

1Was passiert, wenn ich einer declare -i Variable eine ungültige Zahl zuweise?
Bash weist stillschweigend den Wert 0 zu, ohne einen Fehler zu melden. Eingaben sollten deshalb vorher mit einem regulären Ausdruck geprüft werden, bevor sie in eine Integer-Variable geschrieben werden.
2Wirkt declare -x auch rückwirkend auf bereits gestartete Kindprozesse?
Nein. Export wirkt nur auf Kindprozesse, die nach der Deklaration gestartet werden. Bereits laufende Prozesse haben ihr Environment schon vor dem Start kopiert bekommen und sehen spätere Änderungen nicht.
3Muss declare -A vor jeder Zuweisung stehen oder reicht die erste Verwendung?
declare -A muss vor der allerersten Zuweisung stehen. Ohne diese Deklaration legt Bash automatisch ein indiziertes Array an, in dem String-Schlüssel als 0 ausgewertet werden.
4Wie übergebe ich eine Variable per Referenz an eine Funktion?
Mit declare -n oder local -n innerhalb der Funktion, gefolgt vom Namen der Zielvariable als Argument. Lesezugriffe und Zuweisungen auf den Nameref wirken dann direkt auf die referenzierte Variable.
5Was passiert bei einem Namenskonflikt zwischen Nameref und Zielvariable?
Bash erkennt die Selbstreferenz und bricht mit einer Fehlermeldung ab. Deshalb sollten lokale Nameref-Namen in Funktionen immer ein eindeutiges Präfix tragen, das im restlichen Skript nicht vorkommt.
6Kann ich eine mit declare -r geschützte Variable später wieder änderbar machen?
Nein, der Schreibschutz gilt für die gesamte restliche Laufzeit des Skripts und lässt sich nicht durch erneutes declare aufheben. Ein neuer Prozess oder Subshell-Aufruf ist der einzige Weg, den Schutz zu umgehen.
7Wozu brauche ich declare -g, wenn Variablen in Bash standardmäßig global sind?
Nur außerhalb von Funktionen sind Variablen automatisch global. Innerhalb einer Funktion erzeugt declare ohne -g eine funktionslokale Variable, die eine gleichnamige globale Variable verdeckt statt sie zu ändern.
8Kann ich mehrere declare-Flags gleichzeitig verwenden?
Ja, zum Beispiel declare -ir für eine schreibgeschützte Ganzzahl oder declare -ax für eine exportierte Integer-Variable. Die Reihenfolge der Buchstaben spielt dabei keine Rolle.
9Erkennt ShellCheck Fehler im Umgang mit declare-Flags?
ShellCheck erkennt einige statische Probleme wie eine fehlende declare -A vor Array-Nutzung, findet aber nicht jeden Laufzeitfehler wie die stille 0-Konvertierung bei ungültigen Integer-Werten.
10Ist declare -i schneller als normale Zeichenketten-Variablen bei Arithmetik?
Der Geschwindigkeitsunterschied ist in der Praxis vernachlässigbar. Der eigentliche Vorteil von declare -i liegt in der automatischen arithmetischen Auswertung bei jeder Zuweisung, nicht in der Performance.