Bash kann rechnen — aber anders, als man es aus C oder Python kennt. Es gibt vier verschiedene Wege, einen einfachen Ausdruck wie 5 + 3 auszuwerten, und alle haben ihre Eigenheiten. Dazu kommt eine harte Einschränkung: Bash kennt keine Gleitkommazahlen. Wer mit Brüchen oder Prozenten rechnet, ruft externe Tools wie bc oder awk. Dieser Artikel zeigt, welche Variante wann passt, welche Operatoren zur Verfügung stehen und wo die typischen Stolperfallen liegen.

(( )) — Arithmetic Compound Command

Das Arithmetic Compound Command ist die moderne, saubere Variante in Bash. Innerhalb der doppelten Klammern wird der Inhalt als arithmetischer Ausdruck ausgewertet, Variablen brauchen kein $, und das Quoting fällt weitgehend weg.

Bash Grundform (( ))
(( a = 5 + 3 ))
echo "$a"
Output
8

Der Rückgabewert von (( )) ist kein Zahlenwert, sondern ein Exit-Code — und der ist umgekehrt zur intuitiven Erwartung: Ist das Resultat des Ausdrucks ungleich Null, gilt der Befehl als erfolgreich (Exit 0). Ist das Resultat Null, gilt er als fehlgeschlagen (Exit 1). Damit lässt sich (( )) direkt als Bedingung verwenden:

Bash (( )) als Bedingung
if (( a > 10 )); then
    echo "groß"
fi

Variablen werden innerhalb von (( )) automatisch dereferenziert — (( a = b + c )) reicht, das $ ist optional und stört eher. Das macht den Code lesbar und vermeidet die typischen Quoting-Fehler.

$(( )) — Arithmetic Expansion

Wenn du den Wert eines Ausdrucks brauchst — etwa zum Zuweisen, Ausgeben oder als Argument — verwendest du die Arithmetic Expansion $(( )). Sie ersetzt den Ausdruck durch sein numerisches Resultat, ähnlich wie $(...) für Command-Substitution.

Bash $(( )) für Werte
result=$(( 5 + 3 ))
echo "$result"

a=4
b=7
echo "$(( a * b ))"
Output
8
28

Die Faustregel ist einfach: (( )) für Anweisungen (Zuweisung, Bedingung, Counter), $(( )) für Ausdrücke, deren Wert weiterverwendet wird. Beide sind Bash-Builtins, beide sind schnell — keine Subshell, kein externer Prozess.

let

let ist ein älterer Bash-Builtin mit demselben Zweck wie (( )). Funktional identisch, aber mit zwei Nachteilen: Argumente mit Leerzeichen müssen gequotet werden, und * wird je nach Aufruf vom Glob-Mechanismus angefasst.

Bash let im Vergleich
let "a = 5 + 3"
let a=5+3
let "c = a * b"

Empfehlung: (( )) bevorzugen. Es liest sich besser, braucht keine Anführungszeichen für Leerzeichen und ist in jedem moderneren Bash-Code der Standard. let siehst du höchstens noch in alten Skripten.

expr

expr ist kein Builtin, sondern ein externer Befehl aus den Coreutils. Jeder Aufruf forkt eine Subshell und lädt das Programm — entsprechend langsam ist das in Schleifen. Außerdem ist die Syntax sperrig: Operatoren und Operanden müssen durch Leerzeichen getrennt werden, und Sonderzeichen wie * brauchen Backslash-Escape gegen die Shell.

Bash expr — alt aber portabel
expr 5 + 3
expr 4 \* 7
result=$(expr 10 / 3)
echo "$result"
Output
8
28
3

Heute ist expr praktisch obsolet. Sinnvoll bleibt es nur in POSIX-Skripten ohne Bash (#!/bin/sh), wo (( )) und $(( )) nicht garantiert sind — wobei selbst POSIX $(( )) schon lange unterstützt.

Operatoren

In (( )) und $(( )) stehen alle aus C bekannten Operatoren zur Verfügung — inklusive Bit-Operationen, Compound-Zuweisungen und Ternär.

KategorieOperatorenBedeutung
Arithmetik+ - * / %Addition, Subtraktion, Multiplikation, ganzzahlige Division, Modulo
Potenz**2**10 ergibt 1024
In/Dekrement++ --Pre- und Post-Form: i++, ++i
Bit-Shift<< >>Links- und Rechts-Verschiebung
Bit-weise& | ^ ~AND, OR, XOR, NOT
Logisch&& || !UND, ODER, NICHT (Kurzschluss-Auswertung)
Vergleich< > <= >= == !=Liefert 1 (wahr) oder 0 (falsch)
Ternär?:(( max = a > b ? a : b ))
Zuweisung=Einfache Zuweisung
Compound+= -= *= /= %= &= |= ^= <<= >>=Operation und Zuweisung in einem Schritt

Beispiele für die wichtigsten Compound-Operatoren:

Bash Compound-Zuweisungen
x=10
(( x += 5 ))
echo "$x"

(( x *= 2 ))
echo "$x"

(( max = x > 50 ? x : 50 ))
echo "$max"
Output
15
30
50

Bash kennt kein Float

Eine harte Wahrheit: Bash rechnet ausschließlich mit Integer. Jede Division ist eine Ganzzahl-Division, alles hinter dem Komma fällt weg.

Bash Integer-Division
echo "$(( 1 / 2 ))"
echo "$(( 7 / 3 ))"
echo "$(( 10 / 4 ))"
Output
0
2
2

Wer Float braucht, ruft ein externes Werkzeug — meistens bc mit der Option -l (lädt die Math-Library und setzt scale), awk mit BEGIN-Block oder Python.

Bash Float mit bc
echo "scale=2; 1/2" | bc
bc -l <<< "scale=4; 22/7"
Output
.50
3.1428
Bash Float mit awk
awk 'BEGIN { printf "%.4f\n", 22/7 }'
Output
3.1429
Bash Float mit Python
python3 -c "print(1/3)"
Output
0.3333333333333333

Welches Tool? bc ist die klassische Wahl, in jeder Distro verfügbar. awk ist meist schon da, weil es ohnehin für Textverarbeitung gebraucht wird. python3 ist in Skripten praktisch, wenn ohnehin Python-Logik beteiligt ist — sonst Overkill.

Praxis-Patterns

Ein paar konkrete Muster, die in echten Skripten immer wieder auftauchen.

Counter inkrementieren — der Klassiker in Schleifen. i++ ist Post-Inkrement, gibt also den alten Wert zurück und erhöht danach. Innerhalb von (( )) reicht das ohne weitere Verpackung.

Bash Counter
i=0
for f in &#42;.log; do
    (( i++ ))
done
echo "$i Dateien"

Bedingungs-Counter — eine Bedingung als Zahl direkt in eine Summe einrechnen. Vergleiche liefern in (( )) 0 oder 1, und += addiert das einfach drauf.

Bash Bedingungs-Counter
found=0
for n in 3 7 12 4 9; do
    (( found += n &gt; 5 ))
done
echo "$found Werte über 5"

Bit-Flags setzen — eine Maske mit OR-Zuweisung in eine Flag-Variable schreiben. Klassisch für Permission-Bits, Feature-Flags oder Hardware-Register.

Bash Bit-Flags
READ=1
WRITE=2
EXEC=4

flags=0
(( flags &#124;= READ ))
(( flags &#124;= EXEC ))
echo "$flags"

Float-Division mit bc — Hier-String an bc schicken. scale=2 bestimmt die Nachkomma-Stellen, -l aktiviert die Math-Library. Praktisch für Prozent-Berechnungen oder Mittelwerte.

Bash Float-Division
ergebnis=$(bc -l <<< "scale=2; 5/3")
echo "$ergebnis"
Output
1.66

Mittelwert mit awk — Werte als Variablen an awk übergeben (-v), im BEGIN-Block die Rechnung ausführen. Vorteil: keine Pipe, kein Parsing, klare Trennung von Bash-Variablen und awk-Logik.

Bash Average mit awk
sum=42
n=7
awk -v s="$sum" -v n="$n" 'BEGIN { print s/n }'
Output
6

Basis-Konvertierung — In (( )) lassen sich Literale in beliebiger Basis schreiben: BASIS#WERT. Das macht Hex-, Oktal- oder Binär-Werte in Skripten lesbar.

Bash Hex und Oktal in (( ))
(( h = 16#FF ))
echo "$h"

(( o = 8#777 ))
echo "$o"

(( b = 2#1010 ))
echo "$b"
Output
255
511
10

Häufige Stolperfallen

Exit-Code von (( )) ist invertiert

(( )) liefert Exit 0, wenn das Resultat ungleich Null ist, und Exit 1, wenn es Null ist. Das ist sinnvoll als Bedingung (if (( x )) heißt „x ist ungleich 0”), kollidiert aber unangenehm mit set -e: Eine harmlose Zuweisung wie (( count = 0 )) bricht das Skript ab, weil das Resultat 0 ist. Workaround: (( count = 0 )) || true oder direkte Zuweisung mit count=0 ohne (( )).

Modulo bei negativen Zahlen ist Bash-spezifisch

Bei (( -7 % 3 )) liefert Bash -1, nicht 2. Das Vorzeichen folgt dem Dividenden — anders als in Python (-7 % 3 == 2). Wer das Verhalten von Python erwartet, baut sich einen Fehler ein. Robust ist (( ((a % m) + m) % m )), um immer einen nicht-negativen Rest zu bekommen.

* in $(( )) ist sicher, in let und expr nicht

Innerhalb von $(( a &#42; b )) und (( a &#42; b )) wird der Stern nicht vom Glob-Mechanismus angefasst. Bei let und besonders bei expr schlägt das Globbing zu, sobald passende Dateinamen im Verzeichnis liegen. expr 4 &#42; 7 muss deshalb als expr 4 \&#42; 7 geschrieben werden — sonst wird &#42; durch die Dateiliste ersetzt und expr meldet einen Syntaxfehler.

Variablen brauchen kein $ in (( ))

(( a = b + c )) ist äquivalent zu (( a = $b + $c )) — und die erste Form ist lesbarer. Das $ schadet nicht, ist aber überflüssig. Viel wichtiger: niemals quoten innerhalb von (( )). (( "$a" + "$b" )) ist ein Syntaxfehler, weil die Quotes Teil des Ausdrucks werden.

Float-Werte aus bc mit User-Input — Code-Injection-Falle

bc parst alles, was reinkommt, als bc-Sprache — inklusive Funktionsaufrufen und Variablen-Definitionen. Wer User-Input direkt in bc <<< "$user" schickt, öffnet eine Hintertür. Beispiel: user='5; halt; print 99' wird sauber ausgeführt. Lösung: Input mit einer Regex auf Ziffern und einen einzelnen Punkt validieren, bevor er an bc geht.

$(( )) ist Integer — keine implizite Float-Promotion

Anders als in Python oder JavaScript wird in $(( )) nichts automatisch in Float konvertiert. $(( 7 / 2 )) ist 3, nicht 3.5. Wer das übersieht, baut Prozent-Berechnungen mit Off-by-Margin-Fehlern ein. Faustregel: Sobald irgendwo Division mit Brüchen vorkommt, raus aus (( )) und rein in bc oder awk.

expr trennt Operatoren mit Leerzeichen — sonst String-Konkatenation

expr 5+3 liefert nicht 8, sondern den String 5+3. expr braucht zwingend Leerzeichen zwischen jedem Operator und Operand: expr 5 + 3. Dazu kommt das Globbing für &#42; und die Tatsache, dass jeder Aufruf einen externen Prozess startet. Drei gute Gründe, expr heute nur noch in echtem POSIX-Code zu nutzen.

Weiterführende Ressourcen

Externe Quellen

  • Bedingungenif, [[ ]] und numerische Vergleiche
  • Schleifenfor, while und C-style-Schleifen mit (( ))
  • Funktionen — Rückgabewerte, Parameter und lokale Variablen
  • Exit-Codes — Was Exit-Codes bedeuten und warum (( )) invertiert ist
  • Skript-Grundlagen — Shebang, robuster Header und set -euo pipefail
/ Weiter

Zurück zu Shell-Scripting

Zur Übersicht