Bash-Portabilität
Anleitung zum Schreiben portabler POSIX-kompatibler Skripte und zur Entscheidung, wann Bash-spezifische Funktionen eingesetzt werden sollten.
Shebang-Auswahl
#!/usr/bin/env bash für Bash-Skripte verwenden
#!/usr/bin/env bash
Warum: Durchsucht den PATH nach bash und funktioniert systemübergreifend, auch wenn bash in unterschiedlichen Verzeichnissen installiert ist.
#!/bin/sh für POSIX-Skripte verwenden
#!/bin/sh
Warum: Maximale Portabilität, wenn Bash-Funktionen nicht benötigt werden. Auf vielen Systemen ist /bin/sh dash oder eine andere POSIX-Shell.
Direkter Pfad, wenn erforderlich
#!/bin/bash
Nur verwenden, wenn: Systemanforderungen den Bash-Speicherort garantieren oder Sicherheitsrichtlinien absolute Pfade vorschreiben.
POSIX- vs. Bash-Funktionsmatrix
| Feature | POSIX | Bash | Empfehlung |
|---|---|---|---|
[[ ]] | Nein | Ja | Für POSIX [ ] verwenden |
(( )) | Nein | Ja | [ ] mit -eq usw. verwenden |
| Arrays | Nein | Ja | Positionsparameter oder Dateien |
local | Teilw. | Ja | Im Allgemeinen sicher |
${var,,} | Nein | 4+ | Für POSIX tr verwenden |
<<< | Nein | Ja | echo verwenden |
=~ (Regex) | Nein | Ja | grep oder expr verwenden |
source | Nein | Ja | . (Punkt) Befehl verwenden |
function f() | Nein | Ja | Nur f() verwenden |
$'...' | Nein | Ja | printf verwenden |
{1..10} | Nein | Ja | seq oder while-Schleife nutzen |
POSIX-kompatible Muster
Bedingungen
# POSIX – [ ] mit korrekten Anführungszeichen verwenden
if [ -f "$file" ]; then
echo "Datei existiert"
fi
# Zeichenkettenvergleich
if [ "$var" = "value" ]; then
echo "Übereinstimmung"
fi
# Numerischer Vergleich
if [ "$num" -gt 10 ]; then
echo "Größer"
fi
# Zusammengesetzte Bedingungen
if [ -f "$file" ] && [ -r "$file" ]; then
echo "Lesbare Datei"
fi
Groß-/Kleinschreibung umwandeln (POSIX)
# Kleinschreibung
klein=$(echo "$zeichenkette" | tr '[:upper:]' '[:lower:]')
# Großschreibung
gross=$(echo "$zeichenkette" | tr '[:lower:]' '[:upper:]')
Teilzeichenketten-Operationen (POSIX)
# Teilzeichenkette extrahieren – expr oder cut verwenden
substr=$(expr "$zeichenkette" : '.\{3\}\(.\{5\}\)') # Zeichen 4–8
substr=$(echo "$zeichenkette" | cut -c4-8)
# Zeichenkettenlänge
laenge=$(expr length "$zeichenkette")
laenge=${#zeichenkette} # Dies ist tatsächlich POSIX-konform
Dateien lesen (POSIX)
# Zeile für Zeile
while IFS= read -r zeile; do
echo "$zeile"
done < "$datei"
# Ganze Datei lesen (ohne cat)
inhalt=$(cat "$datei") # cat ist POSIX-konform
Befehlssubstitution
# Moderne Syntax (auch in POSIX bevorzugt)
ergebnis=$(befehl)
# Veraltete Syntax (vermeiden)
ergebnis=`befehl`
# Verschachtelt (daher ist die moderne besser)
ergebnis=$(echo $(datum)) # Klar
ergebnis=`echo \`datum\`` # Escape-Albtraum
Bash-spezifische Funktionen, die sich lohnen
Wenn Portabilität nicht erforderlich ist, verbessern diese Bash-Funktionen die Codequalität:
Erweiterter Test [[ ]]
# Mustervergleich
[[ "$datei" == *.txt ]]
# Regex-Vergleich
[[ "$eingabe" =~ ^[0-9]+$ ]]
# Keine Sorgen um Worttrennung
[[ -f $datei ]] # Anführungszeichen optional (aber weiterhin empfohlen)
# Logische Operatoren innerhalb
[[ -f "$datei" && -r "$datei" ]]
Arrays
# Indizierte Arrays
declare -a dateien=()
dateien+=("eins.txt")
dateien+=("zwei.txt")
for f in "${dateien[@]}"; do
verarbeite "$f"
done
# Assoziative Arrays (Bash 4+)
declare -A konfig
konfig[host]="localhost"
konfig[port]="8080"
Parametererweiterung
# Standardwert
"${var:-default}"
# Groß-/Kleinschreibung umwandeln (Bash 4+)
"${var,,}" # Kleinschreibung
"${var^^}" # Großschreibung
# Teilzeichenkette
"${var:0:10}" # erste 10 Zeichen
"${var: -5}" # letzte 5 Zeichen
# Suchen/Ersetzen
"${var//alt/neu}"
Here-Strings
# Bash
read -r var <<< "Eingabezeichenkette"
# POSIX-Äquivalent
var=$(echo "Eingabezeichenkette")
Prozesssubstitution
# Bash – zwei Befehlsausgaben vergleichen
diff <(sort datei1) <(sort datei2)
# POSIX-Äquivalent (mit temporären Dateien)
sort datei1 > /tmp/sortiert1
sort datei2 > /tmp/sortiert2
diff /tmp/sortiert1 /tmp/sortiert2
Shell-Typ erkennen
# Prüfen, ob in Bash ausgeführt wird
if [ -n "${BASH_VERSION:-}" ]; then
echo "Läuft in Bash"
fi
# Bash-Version für Funktionen prüfen
if [ "${BASH_VERSINFO[0]:-0}" -ge 4 ]; then
echo "Bash 4+ verfügbar"
fi
# Generische Shell-Erkennung
case "${SHELL##*/}" in
bash) echo "bash" ;;
zsh) echo "zsh" ;;
*) echo "andere" ;;
esac
Portable Hilfsfunktionen
Entscheidungsleitfaden zur Portabilität
POSIX verwenden, wenn:
- Skript auf minimalistischen Systemen läuft (Container, eingebettete Systeme)
- Das Zielsystem dash, ash oder busybox sh enthält
- Maximale Kompatibilität erforderlich ist
- Das Skript Teil der Systeminitialisierung ist
Bash verwenden, wenn:
- Zielsysteme garantiert bash enthalten
- Arrays, assoziative Arrays oder Regex benötigt werden
- Komplexe Zeichenkettenmanipulation erforderlich ist
- Die Codeklarheit erheblich verbessert wird
- Interaktive Funktionen benötigt werden
Häufige Portabilitätsfallen
echo vs. printf
# Problematisch – Verhalten variiert
echo -n "kein Zeilenumbruch"
echo -e "mit\tTabulatoren"
# Portabel
printf '%s' "kein Zeilenumbruch"
printf 'mit\tTabulatoren\n'
Variablenzuweisung
# Funktioniert überall
var="wert"
# Kann bei manchen Shells fehlschlagen
var = "wert" # Leerzeichen um = sind falsch
Export mit Zuweisung
# POSIX – getrennte Befehle
var="wert"
export var
# Bash/modern – kombiniert (funktioniert meistens)
export var="wert"
Array-ähnliche Operationen ohne Arrays
# Positionsparameter verwenden
set -- "eintrag1" "eintrag2" "eintrag3"
for eintrag in "$@"; do
echo "$eintrag"
done
# Oder IFS-basiertes Aufteilen
eintraege="eintrag1:eintrag2:eintrag3"
IFS=':' read -r eintrag1 eintrag2 eintrag3 <<EOF
$eintraege
EOF