Funktionen in Bash sind keine Funktionen im Sinne von C oder Python — sie sind benannte Blöcke aus Shell-Befehlen, die du wie einen ganz normalen Befehl aufrufst. Sie haben kein eigenes Typ-System, geben keine Werte im klassischen Sinn zurück und teilen sich standardmäßig den Variablen-Namensraum mit dem aufrufenden Skript. Wer das Modell versteht, vermeidet die klassischen Stolperfallen — vergessenes local, missverstandenes return, exportierte Funktionen, die plötzlich nicht mehr verfügbar sind.
Was Bash-Funktionen sind
In klassischen Programmiersprachen sind Funktionen Konstrukte mit eigenen Parametern, Rückgabetypen und einem isolierten Scope. In Bash ist das anders: Eine Funktion ist im Grunde ein Alias für eine Sequenz von Befehlen. Du gibst ihr einen Namen, hinterlegst einen Block und rufst sie genauso auf wie ls oder grep — ohne Klammern, mit Argumenten dahinter.
Die Shell parsiert das Skript einmal von oben nach unten. Bei einer Funktionsdefinition merkt sie sich nur den Namen und den Rumpf; ausgefuehrt wird nichts. Erst beim Aufruf wird der Rumpf in der aktuellen Shell-Umgebung abgearbeitet — kein neuer Prozess, kein neuer Interpreter, keine Argumentkonvertierung.
| Konzept | Bash-Funktion | Funktion in C / Python |
|---|---|---|
| Rückgabewert | Exit-Code 0–255 | Beliebiger Typ |
| Typsystem | Keines, alles ist String | Strikt oder dynamisch |
| Scope | Standardmäßig global | Lokal pro Funktion |
| Parameter | Positional via $1, $2, … | Benannt mit Typ |
| Aufrufsyntax | name arg1 arg2 | name(arg1, arg2) |
Diese Schlichtheit ist Stärke und Falle zugleich: Funktionen lassen sich extrem schnell zusammenstecken, können aber unbemerkt globale Variablen überschreiben oder Exit-Codes verschlucken.
Definition
Bash kennt zwei Schreibweisen für Funktionsdefinitionen. Beide sind funktional äquivalent, aber nicht beide sind portabel.
gruss() {
echo "Hallo Welt"
}function gruss {
echo "Hallo Welt"
}Die erste Form mit den Klammern ist POSIX-konform und läuft in jeder Bourne-kompatiblen Shell — Bash, Dash, Zsh, Ksh. Die zweite Form mit dem Schlüsselwort function funktioniert in Bash und Zsh, aber nicht in Dash. Da /bin/sh auf vielen Systemen ein Symlink auf Dash ist, ist die erste Variante die sichere Wahl.
Aufgerufen wird die Funktion wie ein Befehl — ohne Klammern:
grussHallo WeltWichtig: Die Funktion muss vor dem Aufruf definiert sein. Die Shell parsiert das Skript sequentiell und kennt eine Funktion erst, nachdem sie auf die Definition gestossen ist. Das ist ein typischer Anfängerfehler — Funktionen am Ende des Skripts zu definieren und sie weiter oben aufzurufen.
Parameter
Funktionsargumente werden über dieselben Positionsparameter angesprochen wie Skript-Argumente: $1, $2, $3 und so weiter. Innerhalb der Funktion überschreiben sie die Skript-Argumente lokal — nach dem Funktionsende sind die alten Werte wieder gueltig.
addiere() {
echo "Erstes Argument: $1"
echo "Zweites Argument: $2"
echo "Anzahl Argumente: $#"
echo "Alle Argumente: $@"
}
addiere 5 7Erstes Argument: 5
Zweites Argument: 7
Anzahl Argumente: 2
Alle Argumente: 5 7Die wichtigsten Spezialvariablen innerhalb einer Funktion:
| Variable | Bedeutung |
|---|---|
$1, $2, … | Einzelne Positionsargumente |
$# | Anzahl der Argumente |
$@ | Alle Argumente, in Anführungszeichen einzeln behandelt |
$* | Alle Argumente als ein einziger String |
$0 | Skriptname (nicht der Funktionsname!) |
$FUNCNAME | Name der aktuell laufenden Funktion |
Eine Detail-Falle: $0 zeigt nicht den Funktionsnamen, sondern weiterhin den Skriptnamen. Wer den Funktionsnamen braucht — etwa für Logging — verwendet $FUNCNAME. Eine ausführliche Behandlung der Positionsparameter findest du im Artikel zu Parametern.
Rückgabewerte — drei Wege
Bash kennt keinen Rückgabewert im Sinne anderer Sprachen. Stattdessen gibt es drei pragmatische Wege, ein Ergebnis aus einer Funktion herauszuholen — jeder mit eigenen Vor- und Nachteilen.
Weg 1: return N als Exit-Code
Mit return setzt du den Exit-Code der Funktion. Der Wert muss eine ganze Zahl zwischen 0 und 255 sein. Konvention: 0 bedeutet Erfolg, alles andere ist ein Fehler.
ist_gerade() {
if (( $1 % 2 == 0 )); then
return 0
else
return 1
fi
}
if ist_gerade 4; then
echo "Vier ist gerade"
fiVier ist geradeDer Exit-Code steht nach dem Aufruf in $? zur Verfügung. return ist nur für Status-Informationen geeignet — niemals für Werte wie eine berechnete Summe oder einen Dateinamen.
Weg 2: Output via echo und Capture
Wer einen “echten” Wert zurückgeben will, schreibt ihn auf stdout und fängt ihn beim Aufruf mit Command Substitution ab.
addiere() {
echo $(( $1 + $2 ))
}
ergebnis=$(addiere 5 7)
echo "Summe: $ergebnis"Summe: 12Das ist der idiomatische Weg für beliebige Datentypen — Strings, Zahlen, ganze Listen. Achtung: Innerhalb der Funktion darf dann nichts anderes auf stdout geschrieben werden, sonst landet der “Mist” mit im Ergebnis. Logging gehört in solchen Funktionen auf stderr (siehe Praxis-Patterns).
Weg 3: Globale Variable setzen
Pragmatisch, aber unsauber: Die Funktion schreibt in eine globale Variable, der Aufrufer liest sie aus.
berechne() {
ERGEBNIS=$(( $1 * $2 ))
}
berechne 6 7
echo "Ergebnis: $ERGEBNIS"Wird oft eingesetzt, um die Kosten einer Subshell ($(...) startet einen Kindprozess) zu sparen — etwa in performance-kritischen Schleifen mit Tausenden Aufrufen. Bash-Profis nennen das “Output-Variable Pattern”. Sauberer ist es, den Variablennamen als Parameter zu übergeben und mit printf -v zu schreiben, aber das geht über dieses Kapitel hinaus.
| Weg | Vorteil | Nachteil |
|---|---|---|
return N | Direkt mit if/&& nutzbar, kein Subshell-Overhead | Nur 0–255, nur für Status |
echo + $(...) | Beliebige Werte, sauber und idiomatisch | Subshell kostet Performance, kein Logging auf stdout |
| Globale Variable | Schnell, keine Subshell | Zerbricht Kapselung, Namenskollisionen |
Scope und local
Das ist die wichtigste Lektion überhaupt: In Bash sind alle Variablen standardmäßig global. Eine Zuweisung innerhalb einer Funktion verändert dieselbe Variable, die auch außerhalb sichtbar ist — und überschreibt sie eventuell stillschweigend.
zaehler=42
erhoehe() {
zaehler=$(( zaehler + 1 ))
}
erhoehe
echo "Zaehler ausserhalb: $zaehler"Zaehler ausserhalb: 43Manchmal ist das gewollt — meistens nicht. Mit dem Schlüsselwort local machst du eine Variable funktions-lokal: Sie wird beim Funktionseintritt angelegt und beim Funktionsaustritt wieder verworfen. Eine eventuell vorhandene globale Variable gleichen Namens bleibt unangetastet.
zaehler=42
erhoehe() {
local zaehler=0
zaehler=$(( zaehler + 1 ))
echo "Innen: $zaehler"
}
erhoehe
echo "Aussen: $zaehler"Innen: 1
Aussen: 42Faustregel: Jede Variable, die du innerhalb einer Funktion zuweist, sollte mit local deklariert sein — es sei denn, du willst bewusst eine globale Variable verändern. Diese eine Disziplin verhindert die meisten schwer auffindbaren Bugs in Shell-Skripten.
local ist übrigens ein Bash/Ksh/Zsh-Feature und nicht POSIX. In striktem POSIX-Skripting (#!/bin/sh mit Dash) gibt es das Schlüsselwort nicht.
Funktionen exportieren
Funktionen leben standardmäßig nur in der Shell, in der sie definiert wurden. Startest du aus dieser Shell heraus eine Subshell — etwa mit bash -c "..." oder durch Command Substitution — kennt diese die Funktion nicht.
Mit export -f machst du eine Funktion für Subshells verfügbar:
gruss() {
echo "Hallo aus exportierter Funktion"
}
export -f gruss
bash -c 'gruss'Klassische Falle: export -f funktioniert nur für Bash-Subshells, nicht für Sub-Skripte, die mit einem eigenen Shebang gestartet werden. Wenn du ./anderes-skript.sh aufrufst, wird ein neuer Bash-Prozess gestartet, der seine eigene Initialisierung durchlaeuft — die exportierte Funktion ist dort meist nicht sichtbar (sie kommt zwar als Umgebungsvariable mit, wird aber nur von Bash-Subshells automatisch importiert).
Wenn du Funktionen wirklich projektweit teilen willst, ist die saubere Lösung, sie in eine separate Datei zu legen und mit source datei.sh (oder . datei.sh) in jedes Skript einzubinden.
Praxis-Patterns
Ein paar Bausteine, die in fast jedem groesseren Shell-Skript wiederkehren.
Helper-Funktion für Logging
log() {
echo "[$(date +%T)] $*" >&2
}
log "Skript gestartet"
log "Verarbeite Datei: input.csv"Wichtig ist hier das >&2 — die Ausgabe geht auf stderr. Damit verschmutzt das Logging nicht den Datenstrom auf stdout, der eventuell weiterverarbeitet wird. $* fängt alle übergebenen Argumente als einen String. So kannst du log "Hallo Welt" aufrufen, ohne die Argumente zählen zu müssen.
Wrapper mit Rückgabewert
ist_aktiv() {
pgrep -f "$1" > /dev/null
}
if ist_aktiv "nginx"; then
echo "nginx laeuft"
else
echo "nginx ist nicht aktiv"
fiDie Funktion nutzt den Exit-Code von pgrep direkt — ohne expliziten return. Das ist idiomatisches Bash: Der Exit-Code des letzten Befehls in der Funktion ist automatisch der Exit-Code der Funktion. Die Umleitung > /dev/null unterdrueckt die Ausgabe, weil hier nur das Ergebnis interessiert.
Stdout-Funktion mit Default
get_user() {
echo "${USER:-anonymous}"
}
aktueller=$(get_user)
echo "Eingeloggt als: $aktueller"Die Konstruktion ${USER:-anonymous} liefert den Wert von USER, falls gesetzt — sonst den Fallback anonymous. Eine kompakte Methode, um robuste Funktionen zu schreiben, die nicht auf ein leeres Environment hereinfallen.
Mit local und Default-Parametern
begruesse() {
local name="${1:-Welt}"
echo "Hallo, $name"
}
begruesse
begruesse "Michael"Hallo, Welt
Hallo, MichaelHier kombiniert sich alles: local schuetzt den Scope, ${1:-Welt} liefert einen Default, wenn kein Argument übergeben wird. Das ist die typische Form einer wiederverwendbaren Funktion.
Rekursion
fakultaet() {
local n=$1
if (( n <= 1 )); then
echo 1
else
local rest=$(fakultaet $(( n - 1 )))
echo $(( n * rest ))
fi
}
fakultaet 5120Bash unterstuetzt Rekursion — aber jeder rekursive Aufruf via $(...) startet eine Subshell, was teuer ist. Für mathematische Berechnungen bist du mit bc, awk oder einem richtigen Skriptinterpreter besser bedient. Rekursion in Bash ist eher für Verzeichnis-Traversierungen oder Konfigurations-Resolution sinnvoll.
Häufige Stolperfallen
Vergessenes local fuehrt zu Variablen-Leaks
Der absolute Klassiker: Zwei verschachtelte Funktionen verwenden beide eine Schleifenvariable i. Ohne local ist i global — die innere Funktion überschreibt den Zähler der äußeren, und die äußere Schleife terminiert nach einer einzigen Iteration oder läuft endlos. Solche Bugs sind extrem schwer zu finden, weil das Skript syntaktisch korrekt ist und nur das Verhalten “irgendwie falsch” wirkt. Disziplin: In jeder Funktion zuerst alle benutzten Variablen mit local deklarieren, dann den Body schreiben.
return ist ein Exit-Code, kein Wert
Aus C oder Python kommend ist die Versuchung gross, return 42 zu schreiben und summe=$(addiere 5 7) zu erwarten. Bash macht etwas völlig anderes: return 42 setzt nur den Exit-Code der Funktion, und $(addiere 5 7) fängt den stdout der Funktion ab — der in diesem Fall leer ist. Wer einen Wert braucht, schreibt ihn mit echo auf stdout. Wer einen Status braucht, nimmt return. Niemals beides verwechseln.
Funktion vor Aufruf definieren
Bash parsiert sequentiell. Eine Funktion, die in Zeile 50 definiert wird, kann in Zeile 10 nicht aufgerufen werden — die Shell wirft command not found. Konvention in groesseren Skripten: Erst alle Funktionen definieren (oben), dann der eigentliche Skript-Code (unten), oft eine main-Funktion am Ende, die als Letztes aufgerufen wird.
Funktionsname mit Bindestrich verboten
mein-helfer() { ... } ist syntaktisch ungueltig — Bash interpretiert den Bindestrich als Operator. Erlaubt sind Buchstaben, Ziffern und Underscores, das erste Zeichen muss ein Buchstabe oder Underscore sein. Verwende also mein_helfer statt mein-helfer. Manche Tools wie Programme aus $PATH duerfen Bindestriche enthalten, Funktionen nicht.
set -e wird in Funktionen teilweise ignoriert
Mit set -e soll ein Skript bei jedem Fehler abbrechen. In Funktionen, die in Bedingungen aufgerufen werden — etwa if meine_funktion; then ... oder meine_funktion && weiter — ist set -e aber komplett ausgeschaltet, auch für alle inneren Befehle. Das ist eine der am häufigsten missverstandenen Bash-Regeln. Wer wirklich strict-mode will, sollte sich nicht blind auf set -e verlassen, sondern Fehler explizit prüfen und mit || return 1 weiterreichen.
Anzahl der Argumente nicht via length, sondern $#
Aus Python kommt der Reflex len(args) — in Bash gibt es das nicht. Die Anzahl der Funktionsargumente steht in $#. Ein typisches Pattern am Anfang einer Funktion: if (( $# < 2 )); then echo "Brauche zwei Argumente" >&2; return 1; fi. So vermeidest du, dass die Funktion mit halben Eingaben weiterarbeitet.
IFS-Änderungen mit local IFS einschraenken
Der Internal Field Separator IFS steuert, wie Bash Strings in Felder zerlegt — fundamental für read, for und Wort-Splitting. Wer ihn in einer Funktion umstellt (etwa auf Komma für CSV-Parsing), sollte ihn unbedingt mit local IFS=',' deklarieren. Sonst wirkt die Änderung global weiter, und der Rest des Skripts bricht an unerwarteten Stellen, weil die Wort-Trennung nicht mehr stimmt. local rettet hier vor einem der unangenehmsten Bash-Bugs.
Weiterfuehrende Ressourcen
Externe Quellen
- Bash-Manpage: Functions (man7.org) — Offizielle Referenz zu Funktionsdefinitionen
- GNU Bash-Handbuch: Shell Functions — Vollstaendige Dokumentation mit Beispielen
- BashFAQ: Functions — Praxiserprobte Antworten zu Scope, Rückgabewerten und Export
- Greg’s Wiki: Bash Pitfalls — Sammlung typischer Fehler, viele rund um Funktionen
- Advanced Bash-Scripting Guide: Functions — Tiefer Einblick mit zahlreichen Beispielen
Verwandte Artikel
- Parameter und Argumente — Positionsparameter und Argument-Handling im Detail
- Exit-Codes — Wie Bash Erfolg und Fehler signalisiert
- Fehlerbehandlung —
set -e,trapund robuste Skripte - Schleifen —
for,whileunduntilin Skripten - Skript-Grundlagen — Shebang, Ausführungsrechte und Aufbau eines Skripts