Die Macher der Bash spendierten der Shell Rechenkenntnisse auf Vierte-Klasse-Niveau. Wer mehr als die Grundrechenarten benötigt, weicht auf bc aus. Dass die Ergebnisse rechnender Bash-Skripte trotz der simplen Möglichkeiten jedes Mal stimmen, ist gleichwohl nicht gesagt.
Als Mathe-Schülerin ist die Bash nur Mittelmaß. Zum Glück steht das Fach selten auf dem Stundenplan: Im Alltag brauchen Programmierer meist nur Zählvariablen, sie wandeln Returncodes um oder berechnen hin und wieder Blockgrößen. Für diese Aufgaben ist die Bash mächtig genug. Das Listing 1 zeigt fünf Möglichkeiten, zwei Zahlen zu addieren. Alle Varianten führen normalerweise zum gleichen Ergebnis. Soweit die gute Nachricht.
Aber die Bash wäre nicht die Bash, wenn nicht irgendwelche mehr oder minder seltenen Ausnahmefälle zu Syntaxfehlern führen würden. Wer mag, kann vor dem Weiterlesen Listing 1 selbst auf Fallstricke untersuchen.
Die Zeile 6 spielt eine Sonderrolle. Diese Syntaxvariante funktioniert zwar noch, ist aber nicht mehr dokumentiert. Jeder Bash-Programmierer sollte sie aus seinem Repertoire streichen. Die Zeile 5 verwendet ein externes Programm. Wer auf portable Skripte Wert legt, sollte diese Variante wählen – selbst Systeme mit extrem funktionsbeschnittenen Shells verfügen typischerweise über das »expr« -Kommando.
Auch Zeile 7 gewinnt etwas an Portabilität, wenn der innere Ausdruck durch Leerzeichen von den Klammern abgetrennt ist. Neben der Portabilität spielt die Robustheit eines Skripts eine tragende Rolle – einige der gezeigten Varianten erweisen sich als wenig robust.
Listing 1
Syntaxvarianten für die Addition
01 #!/bin/bash 02 03 a=1 04 b=9 05 c1=$(expr $a + $b) 06 c2=$[a+b] 07 c3=$((a+b)) 08 let c4=$a+$b 09 let c5=a+b 10 11 echo -e "c1=$c1\nc2=$c2\nc3=$c3\nc4=$c4\nc5=$c5"
Fehlende Initialisierung
Die beiden Alternativen in den Zeilen 8 und 9 verhalten sich unterschiedlich, je nachdem, ob die Variablen initialisiert sind oder nicht. Die Bash weist nicht initialisierten Variablen den Wert »0« zu. Wenn »b« nicht gesetzt ist, dann führt die Zeile 8 zu einem Syntaxfehler, während Zeile 9 funktioniert. Auch der Variante mit »expr« bereiten nicht initialisierte Variablen Probleme.
Im Normalfall ist also die Variante in Zeile 9 ohne Variablenauflösung sinnvoller, der Shellcode ist auch besser lesbar. Skripte wie »lavtc.sh« aus den Mjpeg Tools [1] würden dann auch von der dadurch erhöhten Lesbarkeit profitieren (Listing 2).
Die Variante in Zeile 7 von Listing 1 – “Arithmetic Expansion” genannt – verhält sich, was uninitialisierte Variablen angeht, wie die in Zeile 9. Welche Ausführung als lesbarer erscheint, ist Geschmackssache. Die Arithmetic Expansion lässt sich aber universeller einsetzen. Doch leider gibt es Situationen, in denen auch diese robusten Varianten scheitern.
Listing 2
Auszug aus lavtc.sh
01 setResizeArgs ()
02 {
03 let clippedHeight="${videoHeight}-${clipTop}-${clipBottom}"
04 let clippedWidth="${videoWidth}-${clipLeft}-${clipRight}"
05 if [[ -n ${resize} ]] ; then
06 videoOutWidth=${resize%%[x./-, ]*}
07 videoOutHeight=${resize##*[x./-, ]}
08 assertNumber ${videoOutWidth} "target width"
09 assertNumber ${videoOutHeight} "target height"
10 else
11 assertNumber ${videoScale} "output scaling factor" .
12 videoScale=${videoScale-1.00}
13 videoOutWidth=`dc -e "0 k ${clippedWidth} ${videoScale} * 1 / p"`
14 videoOutHeight=`dc -e "0 k ${clippedHeight} ${videoScale} * 1 / p"`
15 fi
16 if [[ ${videoOutHeight} != ${clippedHeight} || ${videoOutWidth} != ${clippedWidth} ]] ; then
17 resizeArgs="-Z ${videoOutWidth}x${videoOutHeight}"
18 fi
19 }
Eine explizit stabile Basis sorgt für Robustheit
Die Seite [2] gibt einen Überblick über das Rechnen mit der Bash. Am Ende steht ein Beispiel, das zu einem gegebenen Monat den Vormonat (dezimal in zwei Stellen) berechnet:
newmonth=$(( (month + 10) % 12 + 101 ))
newmonth=${newmonth:1:2}
Dummerweise funktioniert die erste Zeile nicht, falls »month« selbst zweistellig ist und es sich um den August oder September handelt. Denn die Bash interpretiert Zahlen mit führenden Nullen als Zahlen in Oktaldarstellung, und »08« und »09« sind dabei ungültig. Von den Varianten in Listing 1 hat nur der Ausdruck mit »expr« mit führenden Nullen keine Probleme. Die explizite Angabe der Basis 10 löst das Problem, sobald man in Zeile 1 »month« einfach durch
10#$month
ersetzt. Selbst wenn »month« leer ist, gibt es keinen Syntaxfehler, denn »10#« interpretiert die Bash als Null. Die Bash unterstützt Basen von 2 bis 64, damit sind zum Beispiel Umwandlungen von und nach Hexadezimal recht einfach.
Viele Rechenoperatoren, aber nur Integerzahlen
Neben den Grundrechenarten beherrscht die Bash die ganze Palette an Operatoren, die man auch von höheren Programmiersprachen kennt, zum Beispiel Pre- und Post-Increment, diverse Bit- oder Vergleichsoperatoren. Die Sprache C ist hier das Vorbild. Größte Einschränkung: Die Bash rechnet immer nur mit Integerzahlen, 5 durch 3 ist also 1.
Ein explizites Limit gibt das Bash-Manual nicht an. Einfach hochzuzählen ist eine schlechte Idee, da nach 263-1 der Wert -263 folgt. Im Gegensatz zum Überlauf führt die Division durch null zu einem Fehler.
Kalkulieren mit BC
Integer-Arithmetik langt nicht für alle Aufgaben. Wer Gleitkommazahlen braucht, weicht auf »bc« (Basic Calculator, [3]) aus. Das Posix-Urgestein nimmt mathematische Ausdrücke über Stdin entgegen. Ein einfaches
bc <<< "5/3"
führt jedoch nur zu dem bekannten Ergebnis »1« . Erst wenn der so genannte »scale« -Parameter gesetzt ist, rechnet »bc« mit Nachkommastellen:
bc <<< "scale=2; 5/3"
Selbst hier lauert ein Fallstrick, denn
bc <<< "scale=2; 5"
gibt die Zahl Fünf eben nicht mit zwei Nachkommastellen aus. Zum gewünschten Ergebnis führt erst der Term:
bc <<< "scale=2; 5/1"
Ein Blick in die Manpage lohnt, denn »bc« bietet neben den bei Programmiersprachen üblichen Kontrollstrukturen (Verzweigungen, Schleifen, selbst definierte Funktionen) auch einen Satz mathematischer Funktionen, die beim Rechnen nützlich sind (siehe Abbildung 1).

Abbildung 1: Die Manpage von »bc« referenziert diverse mathematische Funktionen und erläutert deren Anwendung.
Polnisch für Fortgeschrittene
Freunde obskuren Codes nutzen übrigens nicht »bc« , sondern das Tool »dc« [4]. Die Lösung der Aufgabenstellung von eben gestaltet sich damit so:
dc <<< "2 k 5 3 / p"
Wer als Schüler in der Pause bei “Mathe ist doof”-Rufen nicht genickt hat, erkennt die umgekehrte Polnische Notation.
Fazit: Drei, setzen!
Das Fach Mathematik ist nicht die große Stärke der Bash, dafür ist sie auch nicht gemacht. Wenn die Bordmittel nicht reichen, helfen »bc« oder »dc« weiter. Und wer selbst damit nicht mehr weiterkommt, sollte sich fragen, ob nicht ein Programm in einer Hochsprache der bessere Ansatz wäre.
Infos
- Mjpeg Tools: http://mjpeg.sourceforge.net
- “Bash math” mit vielen Beispielen: http://www.mainstreetanswers.org/bash/math.php
- BC: http://www.gnu.org/software/bc/manual/html_mono/bc.html
- DC: http://www.gnu.org/software/bc/manual/dc-1.05/html_mono/dc.html






