Wir schreiben bei NETWAYS häufig Bash-Skripte für Prototyping und komplexe Ablaufsteuerungen. Da sich die Skriptsprache über die Jahre hinweg entwickelt hat, nehme ich gelegentlich etwas Zeit, um meinen Stil zu verbessern.
Um die Beispiele zu verdeutlichen, habe ich folgendes TestSkript erstellt. Dieses nimmt Daten entgegen, schreibt sie in eine temporäre Datei und benennt diese dann in die Zieldatei um (Atomic Commit).
#!/bin/bash
# 1. Bashoptionen anpassen
set -o errexit
set -o pipefail
set -o nounset
set -o errtrace
# 2. Exit Trap installieren
function exit_trap {
local exit_code=$1
local line_no=$2
if [ "$exit_code" != "0" ]; then
echo "Error $exit_code occurred on $line_no"
fi
echo -n "Exit, cleanup ... "
rm -rf "$TEMP_FILE"
echo "ok"
}
trap 'exit_trap $? $LINENO' EXIT SIGINT SIGTERM
TEMP_FILE=$(mktemp)
# 3. Workdir sicher auslesen
DIR=$(cd "$(dirname "$0")"; pwd -P)
for cmd in mv rm mktemp cat; do
# 4. STDERR zu STDOUT
type "$cmd" >/dev/null 2>&1 || {
echo "$cmd is required but not installed. Aborting." >&2
exit 1
}
done
# 5. Parameter Expansion: Standardwerte
CONTENT=${CONTENT:-$(cat)}
TARGET_FILE=${TARGET_FILE:-"$DIR/target.txt"}
# 6. Context, Progress and Error
echo -n "Write content to temp file ... "
echo "$CONTENT" > "$TEMP_FILE"
echo "ok"
echo -n "Move temp file to target ... "
mv "$TEMP_FILE" "$TARGET_FILE"
echo "ok"
1. Bashoptionen anpassen
Die Optionen ‚errexit‘, ‚pipefail‘, ’nounset‘, ‚errtrace‘ machen den Code beim Entwickeln und in der Produktion robust:
- Das Skript stoppt sofort im Falle eines Fehlers
- Der erste Fehler einer Pipeline ist der Fehler des Exitstatus
- Es sind keine undefinierten Variablen erlaubt
- Trap handler werden auch in Subshells installiert
2. Exit Trap installieren
Der Befehl ‚trap‚ reagiert auf Signale innerhalb der Shell. Dies ist nützlich, um auf Fehler und Timeouts zu reagieren und temporäre Ressourcen aufzuräumen.
3. Workdir sicher auslesen
Dieser Weg sichert, dass das Verzeichnis, in dem sich das Skript befindet, korrekt ermittelt wird, egal ob der Aufruf durch ‘./script.sh’ oder ‘bash /usr/local/bin/script.sh’ erfolgt.
4. STDERR to STDOUT (Umleitung)
Falls die Trennung der Deskriptoren von STDOUT und STDERR hinderlich ist, kann STDERR nach STDOUT umgeleitet werden, sodass beide als eine Einheit behandelt werden können. In unserem Beispiel leiten wir die Ausgabe nach ‘/dev/null’ um, machen sie dadurch unsichtbar und reagieren nur auf den Exitstatus.
5. Parameter Expansion: Standardwerte
Wir nutzen Standardwerte, was dem Skript ermöglicht, von außen durch Umgebungsvariablen konfiguriert zu werden, ohne dass der Code selbst geändert werden muss – z.B.:
TARGET_FILE="/tmp/test.txt" CONTENT="test" ./good_shell.sh
6. Context, Progress and Error
Das Platzieren einer Operation innerhalb eines robusten echo-Blocks ermöglicht es, den Kontext und Fehlermeldungen zu sichern. Es kann mühsam sein, auf jeden möglichen Fehler zu reagieren, aber in Kombination mit den Bashoptionen erhält man wichtige Kontextinformationen und Fehlerausgaben, wenn doch mal etwas schiefgeht.
# bash test.sh
Copy something ... test.sh: line 4: /dev/test.txt: Operation not permitted
That’s it! Diese Dinge sind bestimmt nicht der „heilige Gral“. Wenn der Kontext aber stimmt, wird vieles einfacher.


























0 Kommentare