Eine Kernaufgabe im IT-Alltag ist die Fähigkeit, Prozesse zu automatisieren und komplexe Aufgaben zu vereinfachen. Nachdem wir uns im ersten Kapitel mit den fundamentalen Konzepten der Shell und ihrer Funktionsweise vertraut gemacht haben, widmen wir uns nun dem Herzstück effektiver Systemadministration und Entwicklung: der strukturierten Erstellung von Shell-Skripten.
Shell-Skripte bilden das Bindeglied zwischen einfachen Kommandozeilenbefehlen und ausgereiften Automatisierungslösungen. Sie ermöglichen es uns, eine Abfolge von Befehlen in einer Datei zu bündeln, die dann als Einheit ausgeführt werden kann. Dies mag auf den ersten Blick trivial erscheinen, doch die Kraft dieser Methodik entfaltet sich in ihrer vollen Tragweite erst im professionellen Umfeld, wo Konsistenz, Wiederholbarkeit und Effizienz entscheidende Faktoren sind.
Der Übergang von einzelnen Kommandos zu durchdachten Skripten
markiert einen entscheidenden Entwicklungsschritt für jeden IT-Fachmann.
Während ein einzelner Befehl wie ls -la lediglich eine
momentane Aufgabe erfüllt, kann ein gut gestaltetes Skript komplexe
Workflows abbilden, Fehler abfangen und auf unterschiedliche Situationen
reagieren.
Professionelle Shell-Skripte zeichnen sich durch mehrere Qualitätsmerkmale aus:
In diesem Kapitel werden wir diese Grundsätze Schritt für Schritt erarbeiten und anwenden, um den Grundstein für Ihre Fähigkeiten im Shell-Scripting zu legen.
Wie bei jeder technischen Disziplin ist das Beherrschen der Grundlagen im Shell-Scripting entscheidend für den langfristigen Erfolg. Ein solides Fundament ermöglicht es Ihnen:
Die in diesem Kapitel vermittelten Fähigkeiten bilden das Rückgrat Ihrer zukünftigen Expertise. Ähnlich wie ein Gebäude nur so stabil sein kann wie sein Fundament, bestimmt die Qualität Ihrer grundlegenden Skriptierungstechniken maßgeblich Ihre Effektivität bei komplexeren Aufgaben.
Nach Abschluss dieses Kapitels werden Sie in der Lage sein:
Die wahre Stärke des Shell-Scriptings liegt nicht in der theoretischen Kenntnis seiner Syntax, sondern in der praktischen Anwendung zur Lösung realer Probleme. Daher werden wir in diesem Kapitel besonderes Augenmerk auf praxisnahe Beispiele und Übungen legen, die direkt aus dem Alltag von Systemadministratoren und Entwicklern stammen.
Während Sie diese Grundlagen erlernen, werden Sie feststellen, dass selbst einfache Skripte Ihnen erhebliche Zeitersparnis und Konsistenz in Ihrer täglichen Arbeit bieten können. Ein fünfzeiliges Skript, das eine wiederkehrende Aufgabe automatisiert, kann über ein Jahr hinweg leicht hunderte Arbeitsstunden einsparen und gleichzeitig die Fehleranfälligkeit manueller Prozesse eliminieren.
Beim Erlernen des Shell-Scriptings geht es nicht darum, möglichst viele Befehle auswendig zu kennen, sondern vielmehr darum, ein Verständnis für die Konzepte und Denkweisen zu entwickeln, die effektive Skripte auszeichnen. Diese konzeptionelle Herangehensweise wird uns durch das gesamte Kapitel begleiten und Ihnen helfen, nicht nur bestehende Skripte zu verstehen, sondern auch eigene, innovative Lösungen zu entwickeln.
Lassen Sie uns nun gemeinsam in die Welt der Shell-Skripterstellung eintauchen und die Grundlagen legen, die Ihnen den Weg zu fortgeschrittenen Techniken ebnen werden.
Die Entwicklung effektiver Shell-Skripte beginnt mit dem Verständnis ihrer grundlegenden Struktur. Ein gut aufgebautes Bash-Skript folgt bestimmten Konventionen, die nicht nur die Lesbarkeit verbessern, sondern auch die Zuverlässigkeit und Wartbarkeit des Codes erhöhen. In diesem Abschnitt untersuchen wir die wichtigsten Bestandteile eines Bash-Skripts: die Shebang-Zeile, Kommentare und die eigentlichen Befehle.
Die erste Zeile eines Bash-Skripts beginnt typischerweise mit einem sogenannten “Shebang” oder “Hashbang”. Diese spezielle Zeile ist kein gewöhnlicher Kommentar, sondern eine Anweisung an das Betriebssystem, welcher Interpreter für die Ausführung des Skripts verwendet werden soll.
Die Syntax der Shebang-Zeile ist:
#!/pfad/zum/interpreterFür Bash-Skripte wird üblicherweise einer der folgenden Shebangs verwendet:
#!/bin/bashoder
#!/usr/bin/env bashDie Variante #!/bin/bash gibt einen festen Pfad zur Bash
an. Sie funktioniert zuverlässig, solange die Bash tatsächlich unter
/bin/bash installiert ist, was auf den meisten
Linux-Systemen der Fall ist. Auf einigen BSD-Varianten oder exotischen
Systemen könnte sie jedoch an einem anderen Ort installiert sein.
Die Variante #!/usr/bin/env bash ist flexibler. Sie
verwendet das env-Programm, um die Bash in der Umgebung des
Benutzers zu finden, unabhängig davon, wo sie installiert ist. Dies
macht das Skript portabler zwischen verschiedenen Unix-artigen
Betriebssystemen.
Als Best Practice empfiehlt sich für die meisten Anwendungsfälle:
#!/usr/bin/env bashDiese Variante bietet die beste Portabilität und funktioniert auf nahezu allen Unix-Systemen, solange Bash installiert ist.
In einigen Fällen möchten Sie möglicherweise spezielle Optionen an die Bash übergeben. Dies ist ebenfalls über die Shebang-Zeile möglich:
#!/bin/bash -eDie Option -e bewirkt beispielsweise, dass das Skript
sofort beendet wird, wenn ein Befehl mit einem Fehler endet. Dies kann
für kritische Skripte sinnvoll sein, um Folgefehler zu vermeiden.
Eine weitere nützliche Option ist -x, die jede
Befehlszeile vor der Ausführung anzeigt:
#!/bin/bash -xDies ist besonders hilfreich beim Debugging von Skripten.
Kommentare sind für Menschen, nicht für Computer geschrieben. Sie erklären, was der Code tut, warum er es tut und wie er es tut. In Bash gibt es zwei Arten von Kommentaren:
Einzeilige Kommentare beginnen mit dem Symbol # und
erstrecken sich bis zum Ende der Zeile:
# Dies ist ein einzeiliger Kommentar
echo "Hello World" # Dies ist ein Kommentar am Ende einer BefehlszeileBash unterstützt keine nativen mehrzeiligen Kommentarblöcke wie etwa
C oder Java mit /* ... */. Es gibt jedoch einige
Möglichkeiten, mehrzeilige Kommentare zu simulieren:
# Dies ist ein
# mehrzeiliger Kommentar
# mit mehreren Zeilen
: '
Dies ist eine alternative Methode
für mehrzeilige Kommentare in Bash.
Der Doppelpunkt gefolgt von einem String wird evaluiert,
aber nicht ausgegeben.
'Die zweite Methode mit dem Doppelpunkt ist weniger verbreitet, kann
aber nützlich sein, wenn Sie viele Zeilen kommentieren möchten, ohne
jeder Zeile ein # voranzustellen.
Für professionelle Skripte empfiehlt sich eine strukturierte Dokumentation, besonders am Anfang des Skripts. Hier ein Beispiel für einen gut dokumentierten Skriptkopf:
#!/usr/bin/env bash
#
# Dateiname: backup_system.sh
# Beschreibung: Erstellt ein inkrementelles Backup wichtiger Systemdateien
# Autor: Max Mustermann (max.mustermann@example.com)
# Erstellt am: 2023-04-15
# Letzte Änderung: 2023-04-20
# Version: 1.2
#
# Verwendung: ./backup_system.sh [Zielverzeichnis]
# Beispiel: ./backup_system.sh /mnt/backup
#
# Hinweise:
# - Benötigt Root-Rechte für den Zugriff auf Systemdateien
# - Verwendet rsync für die Erstellung inkrementeller Backups
# - Logdateien werden in /var/log/backups gespeichertDiese Art der Dokumentation hilft nicht nur anderen Entwicklern, sondern auch Ihnen selbst, wenn Sie nach Monaten zu Ihrem eigenen Code zurückkehren.
Nach der Shebang-Zeile und den einleitenden Kommentaren folgt der eigentliche Code des Skripts. Dieser besteht aus einer Abfolge von Befehlen, die sequenziell, von oben nach unten, ausgeführt werden.
In Bash wird jede Zeile als separater Befehl interpretiert und ausgeführt:
echo "Schritt 1: Verzeichnis erstellen"
mkdir -p /tmp/beispiel
cd /tmp/beispiel
echo "Aktuelles Verzeichnis: $(pwd)"Mehrere Befehle können auf verschiedene Weisen kombiniert werden:
Sequentielle Ausführung (;):
Befehle werden nacheinander ausgeführt, unabhängig vom Erfolg des
vorherigen Befehls.
echo "Erster Befehl"; echo "Zweiter Befehl"; echo "Dritter Befehl"Bedingte Ausführung (&& und
||): Befehle werden nur ausgeführt, wenn der
vorherige Befehl erfolgreich war (&&) oder
fehlgeschlagen ist (||).
mkdir /tmp/test && echo "Verzeichnis wurde erstellt" || echo "Fehler beim Erstellen des Verzeichnisses"Gruppierung von Befehlen ({ ... } und
( ... )): Mehrere Befehle können zu einer
logischen Einheit gruppiert werden.
# Ausführung in der aktuellen Shell
{ echo "In geschweiften Klammern"; pwd; }
# Ausführung in einer Subshell
( echo "In Klammern"; cd /tmp; pwd )
echo "Aktuelles Verzeichnis ist immer noch: $(pwd)"Der Unterschied zwischen geschweiften Klammern und runden Klammern liegt darin, dass Befehle in runden Klammern in einer Subshell ausgeführt werden, während Befehle in geschweiften Klammern in der aktuellen Shell laufen.
Um die besprochenen Konzepte zu veranschaulichen, betrachten wir nun drei vollständige Beispiele für gut strukturierte Bash-Skripte.
#!/usr/bin/env bash
#
# Dateiname: simple_backup.sh
# Beschreibung: Erstellt ein Backup eines Verzeichnisses mit Zeitstempel
# Autor: Max Mustermann
# Version: 1.0
#
# Verwendung: ./simple_backup.sh [Quellverzeichnis] [Zielverzeichnis]
#
# Fehlermeldung ausgeben und Skript beenden
function error_exit {
echo "FEHLER: $1" >&2
exit 1
}
# Prüfen, ob die erforderlichen Parameter übergeben wurden
if [ $# -ne 2 ]; then
error_exit "Es werden genau zwei Parameter benötigt: Quell- und Zielverzeichnis"
fi
# Parameter in sprechende Variablen speichern
SOURCE_DIR="$1"
TARGET_DIR="$2"
# Prüfen, ob das Quellverzeichnis existiert
if [ ! -d "$SOURCE_DIR" ]; then
error_exit "Das Quellverzeichnis '$SOURCE_DIR' existiert nicht"
fi
# Prüfen, ob das Zielverzeichnis existiert, wenn nicht, erstellen
if [ ! -d "$TARGET_DIR" ]; then
mkdir -p "$TARGET_DIR" || error_exit "Konnte Zielverzeichnis '$TARGET_DIR' nicht erstellen"
echo "Zielverzeichnis '$TARGET_DIR' wurde erstellt"
fi
# Zeitstempel für den Backup-Namen generieren
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
BACKUP_NAME="backup_${TIMESTAMP}.tar.gz"
BACKUP_PATH="${TARGET_DIR}/${BACKUP_NAME}"
# Backup erstellen
echo "Erstelle Backup von '$SOURCE_DIR' nach '$BACKUP_PATH'..."
tar -czf "$BACKUP_PATH" -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")" || \
error_exit "Backup konnte nicht erstellt werden"
echo "Backup erfolgreich erstellt: $BACKUP_PATH"
exit 0Dieses Skript demonstriert: - Eine klare Shebang-Zeile - Einen ausführlichen Dokumentationsheader - Die Verwendung von Funktionen für wiederholte Aufgaben - Fehlerprüfung und -behandlung - Sprechende Variablennamen - Sinnvolle Kommentare zur Erklärung der Logik
#!/usr/bin/env bash
#
# Dateiname: system_monitor.sh
# Beschreibung: Überwacht wichtige Systemressourcen und sendet Warnungen
# Autor: Erika Musterfrau
# Version: 1.1
#
# Verwendung: ./system_monitor.sh [Schwellenwert in Prozent]
#
# Standardwerte setzen
THRESHOLD=${1:-90} # Standardwert 90%, wenn kein Parameter übergeben wurde
LOG_FILE="/var/log/system_monitor.log"
EMAIL="admin@example.com"
# Funktion zur Protokollierung
log_message() {
local message="[$(date '+%Y-%m-%d %H:%M:%S')] $1"
echo "$message" | tee -a "$LOG_FILE"
}
# Funktion zur Benachrichtigung
send_alert() {
local subject="WARNUNG: Hohe Systemauslastung"
local message="$1"
# In einer echten Umgebung würde hier ein E-Mail-Versand stehen
log_message "ALERT: $message"
echo "To: $EMAIL"
echo "Subject: $subject"
echo "Message: $message"
}
# Überprüfen der CPU-Auslastung
check_cpu() {
# CPU-Last der letzten Minute ermitteln
local cpu_load=$(uptime | awk '{print $10}' | tr -d ',')
local cpu_cores=$(nproc)
local cpu_percent=$(echo "scale=2; $cpu_load * 100 / $cpu_cores" | bc)
log_message "CPU-Auslastung: ${cpu_percent}%"
# Warnung ausgeben, wenn Schwellenwert überschritten
if (( $(echo "$cpu_percent > $THRESHOLD" | bc -l) )); then
send_alert "CPU-Auslastung bei ${cpu_percent}% (Schwellenwert: ${THRESHOLD}%)"
fi
}
# Überprüfen der Speicherauslastung
check_memory() {
# Freien und gesamten Speicher ermitteln
local mem_info=$(free | grep Mem)
local total_mem=$(echo "$mem_info" | awk '{print $2}')
local used_mem=$(echo "$mem_info" | awk '{print $3}')
local mem_percent=$(echo "scale=2; $used_mem * 100 / $total_mem" | bc)
log_message "Speicherauslastung: ${mem_percent}%"
# Warnung ausgeben, wenn Schwellenwert überschritten
if (( $(echo "$mem_percent > $THRESHOLD" | bc -l) )); then
send_alert "Speicherauslastung bei ${mem_percent}% (Schwellenwert: ${THRESHOLD}%)"
fi
}
# Überprüfen der Festplattenauslastung
check_disk() {
# Alle gemounteten Dateisysteme prüfen
while read -r filesystem size used avail use_percent mountpoint; do
# Prozentzeichen entfernen
use_percent=${use_percent/\%/}
log_message "Festplattenauslastung $mountpoint: ${use_percent}%"
# Warnung ausgeben, wenn Schwellenwert überschritten
if [ "$use_percent" -gt "$THRESHOLD" ]; then
send_alert "Festplattenauslastung von $mountpoint bei ${use_percent}% (Schwellenwert: ${THRESHOLD}%)"
fi
done < <(df -h | grep -v "Filesystem" | awk '{print $1, $2, $3, $4, $5, $6}')
}
# Hauptteil des Skripts
log_message "Systemüberwachung gestartet (Schwellenwert: ${THRESHOLD}%)"
# Systemressourcen prüfen
check_cpu
check_memory
check_disk
log_message "Systemüberwachung abgeschlossen"
exit 0Dieses Beispiel zeigt: - Die Verwendung von Standardwerten für Parameter - Die Definition und Nutzung von Funktionen für modularen Code - Die strukturierte Protokollierung - Fortgeschrittene Befehls-Verkettung und -Substitution - Die Verwendung von Unterkommandos für komplexere Aufgaben
#!/usr/bin/env bash
#
# Dateiname: interactive_menu.sh
# Beschreibung: Zeigt ein interaktives Menü für häufige Administrationstasks
# Autor: Sven Schmidt
# Version: 0.9 (Beta)
#
# Verwendung: ./interactive_menu.sh
#
# Farbdefinitionen für die Ausgabe
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Funktion zum Anzeigen formatierter Nachrichten
print_message() {
local type=$1
local message=$2
case $type in
"info")
echo -e "${BLUE}[INFO]${NC} $message"
;;
"success")
echo -e "${GREEN}[ERFOLG]${NC} $message"
;;
"warning")
echo -e "${YELLOW}[WARNUNG]${NC} $message"
;;
"error")
echo -e "${RED}[FEHLER]${NC} $message"
;;
*)
echo "$message"
;;
esac
}
# Funktion zur Bestätigung einer Aktion
confirm_action() {
local prompt=$1
local response
echo -e "${YELLOW}$prompt (j/n)${NC}"
read -r response
case $response in
[jJ][aA]|[jJ])
return 0
;;
*)
return 1
;;
esac
}
# Funktion zum Anzeigen des Hauptmenüs
show_menu() {
clear
echo "=========================================="
echo " ADMINISTRATIONS-MENÜ "
echo "=========================================="
echo "1. Systemstatus anzeigen"
echo "2. Benutzerverwaltung"
echo "3. Dateioperationen"
echo "4. Netzwerkdiagnose"
echo "5. Beenden"
echo "=========================================="
echo -n "Bitte wählen Sie eine Option (1-5): "
}
# Funktion zur Anzeige des Systemstatus
show_system_status() {
print_message "info" "Systemstatus wird abgerufen..."
echo "----------------------------------------"
echo "CPU-Auslastung:"
top -bn1 | grep "Cpu(s)" | awk '{print $2 + $4 "% genutzt"}'
echo "----------------------------------------"
echo "Speicherauslastung:"
free -h | grep "Mem:" | awk '{print $3 " von " $2 " genutzt (" $3/$2*100 "%)"}'
echo "----------------------------------------"
echo "Festplattenauslastung:"
df -h | grep -v "tmpfs" | grep -v "Filesystem"
echo "----------------------------------------"
echo "Systemlaufzeit:"
uptime
echo "----------------------------------------"
read -p "Drücken Sie Enter, um fortzufahren..."
}
# Hauptlogik des Skripts
while true; do
show_menu
read -r choice
case $choice in
1)
show_system_status
;;
2)
# Hier würde die Implementierung für Benutzerverwaltung folgen
print_message "info" "Benutzerverwaltung ausgewählt (noch nicht implementiert)"
sleep 2
;;
3)
# Hier würde die Implementierung für Dateioperationen folgen
print_message "info" "Dateioperationen ausgewählt (noch nicht implementiert)"
sleep 2
;;
4)
# Hier würde die Implementierung für Netzwerkdiagnose folgen
print_message "info" "Netzwerkdiagnose ausgewählt (noch nicht implementiert)"
sleep 2
;;
5)
if confirm_action "Möchten Sie das Programm wirklich beenden?"; then
print_message "success" "Programm wird beendet. Auf Wiedersehen!"
exit 0
fi
;;
*)
print_message "error" "Ungültige Auswahl. Bitte wählen Sie eine Option zwischen 1 und 5."
sleep 2
;;
esac
doneDieses Beispiel zeigt: - Die Verwendung von ANSI-Farbcodes für formatierte Ausgaben - Ein interaktives Benutzermenü - Nutzereingaben und deren Verarbeitung - Eine Schleifenstruktur für ein kontinuierliches Menü - Strukturierte Funktionen für verschiedene Funktionalitäten
Basierend auf den vorgestellten Beispielen lassen sich folgende Best Practices für den Aufbau von Bash-Skripten ableiten:
#!/usr/bin/env bash für maximale
PortabilitätMAX_RETRY_COUNT ist besser als mrcset -e, um das Skript bei Fehlern zu
beenden-h oder
--help-OptionDurch die Einhaltung dieser Praktiken werden Ihre Skripte nicht nur besser lesbar und wartbar, sondern auch zuverlässiger und flexibler.
Variablen sind das Rückgrat jeder Programmiersprache und stellen benannte Speicherorte dar, die Daten enthalten können. In Shell-Skripten ermöglichen Variablen die Speicherung von Werten, die später im Skript verwendet werden können, was den Code lesbarer, wartbarer und dynamischer macht. In diesem Abschnitt werden wir die Grundlagen der Variablendeklaration und -verwendung in Bash-Skripten behandeln sowie fortgeschrittene Konzepte wie Scoping und Datentypen erläutern.
In Bash erfolgt die Deklaration einer Variable durch einfache Zuweisung eines Wertes. Die Syntax ist dabei unkompliziert:
variable_name=wertHierbei sind folgende Punkte zu beachten:
Beispiele für korrekte Variablendeklarationen:
name="Max Mustermann" # String mit Leerzeichen, Anführungszeichen notwendig
alter=42 # Zahl, keine Anführungszeichen nötig
datei="/etc/hosts" # Pfad, Anführungszeichen optional
ist_aktiv=true # Boolescher Wert (in Bash als String gespeichert)Beispiele für häufige Fehler bei der Variablendeklaration:
name = "Max Mustermann" # Falsch: Leerzeichen um das Gleichheitszeichen
42alter=42 # Falsch: Variablenname beginnt mit einer Ziffer
user-name="max" # Falsch: Bindestrich im VariablennamenFür Variablennamen in Bash gelten folgende Regeln:
name und NAME sind unterschiedliche
Variablen)if, then,
else) dürfen nicht als Variablennamen verwendet werdenAls bewährte Praxis haben sich folgende Konventionen etabliert:
user_name)MAX_CONNECTIONS)_temp_file)Um auf den Wert einer Variable zuzugreifen, setzen Sie ein
Dollarzeichen ($) vor den Variablennamen:
name="Max"
echo "Hallo, $name!" # Ausgabe: Hallo, Max!Die Verwendung von geschweiften Klammern um den Variablennamen ist optional, wird aber empfohlen, um Mehrdeutigkeiten zu vermeiden:
name="Max"
echo "Hallo, ${name}!" # Empfohlen: Klare Abgrenzung des VariablennamensDie geschweiften Klammern sind besonders wichtig, wenn der Variablenname direkt von anderen Zeichen gefolgt wird:
name="Max"
echo "Hallo, ${name}imus!" # Ausgabe: Hallo, Maximus!
echo "Hallo, $nameimus!" # Fehler oder leere Ausgabe, da $nameimus als Variable interpretiert wirdBash bietet verschiedene Möglichkeiten zur Modifikation von Variablenwerten während des Zugriffs:
# Standardwert festlegen, falls Variable nicht gesetzt oder leer ist
echo "Hallo, ${name:-Gast}!" # Wenn $name leer ist: "Hallo, Gast!"
# Standardwert festlegen und Variable setzen
echo "Hallo, ${name:=Gast}!" # Setzt $name auf "Gast", wenn es leer ist
# Alternativwert ausgeben, wenn Variable gesetzt und nicht leer ist
echo "Status: ${error:+Es ist ein Fehler aufgetreten}"
# Fehlermeldung ausgeben, wenn Variable nicht gesetzt oder leer ist
echo "Benutzer: ${username:?Benutzername nicht angegeben}"
# Substring extrahieren (ab Position 0, 3 Zeichen)
echo "${name:0:3}" # Gibt die ersten 3 Zeichen von $name ausDiese Substitutionsmechanismen sind besonders nützlich für die robuste Fehlerbehandlung und die Verarbeitung von Benutzereingaben.
Obwohl Bash grundsätzlich eine typlose Sprache ist (alle Variablen werden intern als Strings gespeichert), unterstützt sie die Arbeit mit verschiedenen Datentypen:
Strings sind der häufigste Variablentyp in Bash-Skripten:
name="Max Mustermann"
greeting="Hallo, $name"
echo "$greeting" # Ausgabe: Hallo, Max Mustermann
# Mehrere Strings verbinden (Konkatenation)
first_name="Max"
last_name="Mustermann"
full_name="$first_name $last_name"
echo "$full_name" # Ausgabe: Max Mustermann
# Länge eines Strings ermitteln
echo "${#name}" # Ausgabe: 14Für die Arbeit mit Strings bietet Bash verschiedene Operationen zur String-Manipulation:
text="Bash-Scripting ist mächtig"
# Ersetzen des ersten Vorkommens
echo "${text/mächtig/leistungsstark}" # Ausgabe: Bash-Scripting ist leistungsstark
# Ersetzen aller Vorkommen
echo "${text//i/I}" # Ersetzt alle 'i' durch 'I'
# Löschen vom Anfang (kürzeste Übereinstimmung)
echo "${text#Bash-}" # Ausgabe: Scripting ist mächtig
# Löschen vom Anfang (längste Übereinstimmung)
echo "${text##*ist }" # Ausgabe: mächtig
# Löschen vom Ende (kürzeste Übereinstimmung)
echo "${text%mächtig}" # Ausgabe: Bash-Scripting ist
# Löschen vom Ende (längste Übereinstimmung)
echo "${text%%ist*}" # Ausgabe: Bash-Scripting
# Groß-/Kleinschreibung ändern
echo "${text^^}" # Alles in Großbuchstaben
echo "${text,,}" # Alles in KleinbuchstabenObwohl Bash keine nativen numerischen Typen hat, können arithmetische Operationen durchgeführt werden:
# Arithmetische Expansion
a=5
b=3
sum=$((a + b))
echo "$sum" # Ausgabe: 8
# Alternative Syntax
let "product = a * b"
echo "$product" # Ausgabe: 15
# Inkrement/Dekrement
((a++))
echo "$a" # Ausgabe: 6
# Komplexere Ausdrücke
result=$(( (a + b) * 2 ))
echo "$result" # Ausgabe: 18Für komplexere mathematische Operationen oder Fließkommazahlen kann
das Kommandozeilenprogramm bc verwendet werden:
# Berechnung mit Fließkommazahlen
pi=$(echo "scale=10; 4*a(1)" | bc -l)
echo "Pi ≈ $pi"
# Division mit Fließkommazahlen
result=$(echo "scale=2; 5/3" | bc)
echo "$result" # Ausgabe: 1.66Bash unterstützt indizierte Arrays und, ab Bash 4.0, assoziative Arrays (Dictionaries):
Indizierte Arrays (nullbasierter Index):
# Deklaration und Initialisierung
farben=("Rot" "Grün" "Blau")
# Alternative Deklaration
declare -a zahlen
zahlen[0]=1
zahlen[1]=2
zahlen[2]=3
# Zugriff auf Elemente
echo "${farben[0]}" # Ausgabe: Rot
# Alle Elemente ausgeben
echo "${farben[@]}" # Ausgabe: Rot Grün Blau
# Anzahl der Elemente
echo "${#farben[@]}" # Ausgabe: 3
# Element hinzufügen
farben+=("Gelb")
# Indizes auflisten
echo "${!farben[@]}" # Ausgabe: 0 1 2 3
# Element entfernen
unset farben[1] # Entfernt "Grün"
echo "${farben[@]}" # Ausgabe: Rot Blau GelbAssoziative Arrays (Schlüssel-Wert-Paare, ab Bash 4.0):
# Deklaration und Initialisierung
declare -A benutzer
benutzer["name"]="Max"
benutzer["alter"]=30
benutzer["beruf"]="Entwickler"
# Alternative Deklaration
declare -A hauptstaedte=( ["Deutschland"]="Berlin" ["Frankreich"]="Paris" ["Italien"]="Rom" )
# Zugriff auf Elemente
echo "${benutzer["name"]}" # Ausgabe: Max
# Alle Werte ausgeben
echo "${benutzer[@]}" # Ausgabe: Max 30 Entwickler
# Alle Schlüssel ausgeben
echo "${!benutzer[@]}" # Ausgabe: name alter beruf
# Element hinzufügen
benutzer["adresse"]="Musterstraße 1"
# Element entfernen
unset benutzer["beruf"]Im Kontext der Bash-Shell gibt es zwei Hauptebenen für Variablen: globale und lokale Variablen.
Globale Variablen sind innerhalb des gesamten Skripts und in allen Funktionen sichtbar. Standardmäßig sind alle in einem Skript deklarierten Variablen global:
#!/usr/bin/env bash
# Globale Variable
global_var="Ich bin global"
function beispiel_funktion() {
echo "In der Funktion: $global_var"
# Änderung der globalen Variable
global_var="Geändert in der Funktion"
}
echo "Vor dem Funktionsaufruf: $global_var"
beispiel_funktion
echo "Nach dem Funktionsaufruf: $global_var"Ausgabe:
Vor dem Funktionsaufruf: Ich bin global
In der Funktion: Ich bin global
Nach dem Funktionsaufruf: Geändert in der Funktion
Lokale Variablen sind nur innerhalb der Funktion sichtbar, in der sie
deklariert wurden. Sie werden mit dem Schlüsselwort local
deklariert:
#!/usr/bin/env bash
function beispiel_funktion() {
# Lokale Variable
local lokale_var="Ich bin lokal"
echo "In der Funktion: $lokale_var"
}
beispiel_funktion
echo "Nach dem Funktionsaufruf: $lokale_var" # $lokale_var ist hier nicht definiertAusgabe:
In der Funktion: Ich bin lokal
Nach dem Funktionsaufruf:
Die Verwendung lokaler Variablen in Funktionen ist eine wichtige Best Practice, um unbeabsichtigte Seiteneffekte zu vermeiden und die Wartbarkeit des Codes zu verbessern.
Bei der Verschachtelung von Funktionen gelten folgende Regeln:
#!/usr/bin/env bash
global_var="Global"
function aussere_funktion() {
local aussen_var="Außen"
echo "In äußerer Funktion - global_var: $global_var, aussen_var: $aussen_var, innen_var: $innen_var"
function innere_funktion() {
local innen_var="Innen"
echo "In innerer Funktion - global_var: $global_var, aussen_var: $aussen_var, innen_var: $innen_var"
}
innere_funktion
echo "Nach innerer Funktion - global_var: $global_var, aussen_var: $aussen_var, innen_var: $innen_var"
}
aussere_funktionAusgabe:
In äußerer Funktion - global_var: Global, aussen_var: Außen, innen_var:
In innerer Funktion - global_var: Global, aussen_var: Außen, innen_var: Innen
Nach innerer Funktion - global_var: Global, aussen_var: Außen, innen_var:
Hier sehen wir, dass: - Die globale Variable überall sichtbar ist - Die Variable der äußeren Funktion in der inneren Funktion sichtbar ist - Die Variable der inneren Funktion nur in der inneren Funktion sichtbar ist
Bash unterstützt die Deklaration von Konstanten mit dem Befehl
declare und der Option -r (readonly):
# Konstante deklarieren
declare -r PI=3.14159
declare -r MAX_VERSUCHE=3
# Versuch, eine Konstante zu ändern
PI=3.14 # Führt zu einem Fehler: "PI: readonly variable"Für Skripts, die viele Konstanten verwenden, ist es eine gute Praxis, diese am Anfang des Skripts zu deklarieren und in Großbuchstaben zu schreiben, um sie von regulären Variablen zu unterscheiden.
Beim Arbeiten mit Variablen in Bash gibt es einige häufige Fallstricke, die zu schwer zu findenden Fehlern führen können:
Leerzeichen um das Gleichheitszeichen:
name = "Max" # Falsch: Bash interpretiert 'name' als Befehl
name="Max" # KorrektFehlende Anführungszeichen bei Werten mit Leerzeichen:
name=Max Mustermann # Falsch: Bash interpretiert 'Mustermann' als separaten Befehl
name="Max Mustermann" # KorrektFehlende geschweifte Klammern bei Variablenerweiterung:
echo "Hallo $nameErweiterung" # Falsch: Bash sucht nach der Variable $nameErweiterung
echo "Hallo ${name}Erweiterung" # KorrektVergleich von Zahlen mit Zeichenkettenoperatoren:
if [ $zahl = 5 ]; then # Führt zu Fehlern, wenn $zahl leer ist oder Leerzeichen enthält
if [ "$zahl" = 5 ]; then # Besser: Variablenwert in Anführungszeichen
if [ "$zahl" -eq 5 ]; then # Korrekt für numerische VergleicheUnbeabsichtigte Globbing-Expansion:
file="*.txt"
echo $file # Gibt alle .txt-Dateien im aktuellen Verzeichnis aus
echo "$file" # Korrekt: Gibt literal "*.txt" ausVergessen, den Rückgabewert eines Befehls zu überprüfen:
ergebnis=$(befehl_der_fehlschlagen_kann)
echo "$ergebnis" # Möglicherweise leer bei Fehler
# Besser:
if ! ergebnis=$(befehl_der_fehlschlagen_kann); then
echo "Fehler bei der Befehlsausführung"
exit 1
fi
echo "$ergebnis"Versehentliches Überschreiben wichtiger Variablen:
PATH="mein/pfad" # Überschreibt die wichtige Umgebungsvariable PATH
my_path="mein/pfad" # Besser: Eindeutiger NameUm robuste und wartbare Skripte zu schreiben, sollten Sie diese Best Practices befolgen:
Immer aussagekräftige Variablennamen verwenden:
# Schlecht
x=5
# Gut
max_retries=5Konsistenten Stil für Variablennamen verwenden:
# Wählen Sie einen Stil und bleiben Sie dabei
user_name="Max" # snake_case
maxRetries=5 # camelCaseVariablen immer in doppelte Anführungszeichen setzen, wenn sie verwendet werden:
echo "$variable" # Verhindert Probleme mit Leerzeichen und SonderzeichenLokale Variablen in Funktionen verwenden:
function process_file() {
local file="$1" # lokale Variable
# ...
}Standardwerte für Variablen definieren:
name="${1:-Gast}" # Setzt $name auf "Gast", wenn kein Parameter übergeben wirdValidierung von Variablenwerten:
if [[ ! "$zahl" =~ ^[0-9]+$ ]]; then
echo "Fehler: $zahl ist keine Zahl"
exit 1
fiKonstanten für unveränderliche Werte verwenden:
declare -r CONFIG_FILE="/etc/myapp.conf"Verschachtelte Variablenexpansion vorsichtig verwenden:
# Komplex und fehleranfällig
eval "echo \${${prefix}_var}"
# Besser: Assoziatives Array verwenden
declare -A vars
vars["prefix_var"]="Wert"
echo "${vars["${prefix}_var"]}"Umgebungsvariablen sind ein zentraler Bestandteil des Unix/Linux-Betriebssystems und spielen eine wichtige Rolle in der Shell-Programmierung. Sie dienen als globale Einstellungen, die das Verhalten von Programmen und des Systems selbst beeinflussen können. In diesem Abschnitt werden wir uns mit der Bedeutung von Umgebungsvariablen, ihrer Verwendung und den wichtigsten vordefinierten Umgebungsvariablen beschäftigen.
Umgebungsvariablen sind spezielle Variablen, die von der Shell verwaltet werden und an alle Prozesse weitergegeben werden, die von dieser Shell gestartet werden. Sie dienen zur Konfiguration der Systemumgebung und zur Kommunikation zwischen Prozessen.
Im Gegensatz zu regulären Shell-Variablen, die nur innerhalb der aktuellen Shell-Instanz zugänglich sind, werden Umgebungsvariablen an Kindprozesse vererbt. Dies macht sie zu einem mächtigen Werkzeug für die Konfiguration von Anwendungen und Diensten.
Um alle Umgebungsvariablen anzuzeigen, können Sie den Befehl
env oder printenv verwenden:
env # Zeigt alle Umgebungsvariablen an
printenv # Alternative zu env
printenv PATH # Zeigt nur den Wert der PATH-Variable an
echo "$PATH" # Alternative MethodeUmgebungsvariablen können auf verschiedene Weise gesetzt werden:
Temporär für einen einzelnen Befehl:
DEBUG=1 ./mein_skript.sh # Setzt die Umgebungsvariable DEBUG nur für diesen BefehlTemporär für die aktuelle Shell-Sitzung:
export DEBUG=1 # Setzt und exportiert die Variable
./mein_skript.sh # Das Skript kann nun auf $DEBUG zugreifenDauerhaft für einen Benutzer (durch Eintrag in die Shell-Initialisierungsdateien):
# In ~/.bashrc oder ~/.bash_profile hinzufügen:
export EDITOR=vimDauerhaft für alle Benutzer (systemweit):
# In /etc/profile oder eine Datei in /etc/profile.d/ hinzufügen:
export JAVA_HOME=/usr/lib/jvm/default-javaexport und einfacher ZuweisungEs ist wichtig, den Unterschied zwischen einer einfachen Variablenzuweisung und dem Export einer Variable zu verstehen:
# Nur eine Shell-Variable, nicht an Kindprozesse vererbt
MY_VAR="Wert"
# Eine Umgebungsvariable, wird an Kindprozesse vererbt
export MY_ENV_VAR="Wert"Um zu überprüfen, ob eine Variable exportiert wurde:
declare -p MY_VAR # Zeigt Variablendeklaration an, mit oder ohne 'export'-AttributDas Linux/Unix-System und die Bash stellen zahlreiche vordefinierte Umgebungsvariablen bereit:
# Suchpfade für ausführbare Dateien (durch Doppelpunkt getrennt)
echo "$PATH"
# Aktuelles Arbeitsverzeichnis
echo "$PWD"
# Home-Verzeichnis des aktuellen Benutzers
echo "$HOME"
# Temporäres Verzeichnis
echo "$TMPDIR" # Falls nicht gesetzt, wird standardmäßig /tmp verwendet# Aktueller Benutzername
echo "$USER"
echo "$LOGNAME" # Alternative zu USER
# Aktueller Host-Name
echo "$HOSTNAME"# Shell-Typ
echo "$SHELL" # z.B. /bin/bash
# Bash-Version
echo "$BASH_VERSION"
# Umgebungsvariable für die Anzeige des Shell-Prompts
echo "$PS1"
# Standardeditor
echo "$EDITOR"# Spracheinstellungen (bestimmt Ausgabesprache vieler Programme)
echo "$LANG"
echo "$LC_ALL" # Überschreibt alle LC_* Variablen
# Zeichensatzeinstellung
echo "$LC_CTYPE"# Process-ID des aktuellen Shell-Prozesses
echo "$$"
# Process-ID des letzten im Hintergrund gestarteten Prozesses
echo "$!"
# Exit-Status des letzten Befehls
echo "$?"
# Skriptname (in einem Skript)
echo "$0"
# Anzahl der übergebenen Argumente (in einem Skript)
echo "$#"
# Alle übergebenen Argumente (in einem Skript)
echo "$@"Umgebungsvariablen werden häufig verwendet, um das Verhalten von Skripten und Programmen zu steuern. Hier sind einige typische Anwendungsfälle:
#!/usr/bin/env bash
# Verwendung des Home-Verzeichnisses für Konfigurationsdateien
CONFIG_DIR="${HOME}/.myapp"
CONFIG_FILE="${CONFIG_DIR}/config.ini"
# Temporäre Dateien im System-Temp-Verzeichnis
TEMP_DIR="${TMPDIR:-/tmp}"
TEMP_FILE="${TEMP_DIR}/myapp-$$.tmp"
# Erstellen des Konfigurationsverzeichnisses, falls es nicht existiert
mkdir -p "$CONFIG_DIR"#!/usr/bin/env bash
# Debug-Modus aktivieren, wenn DEBUG gesetzt ist
if [ -n "$DEBUG" ]; then
echo "Debug-Modus aktiviert"
set -x # Gibt jeden Befehl vor der Ausführung aus
fi
# Unterschiedliches Verhalten je nach Umgebung
case "${ENVIRONMENT:-production}" in
development)
echo "Entwicklungsumgebung - Verwende lokale Konfiguration"
CONFIG_FILE="./config.dev.ini"
;;
testing)
echo "Testumgebung - Verwende Test-Konfiguration"
CONFIG_FILE="/etc/myapp/config.test.ini"
;;
production)
echo "Produktionsumgebung - Verwende Produktionskonfiguration"
CONFIG_FILE="/etc/myapp/config.prod.ini"
;;
esac#!/usr/bin/env bash
# Prüfen, ob das Terminal Farben unterstützt
if [ -t 1 ] && [ -n "$TERM" ] && [ "$TERM" != "dumb" ]; then
# Terminal unterstützt Farben
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m' # No Color
else
# Keine Farbunterstützung
RED=""
GREEN=""
YELLOW=""
NC=""
fi
# Farbige Ausgabe
echo -e "${GREEN}Erfolg:${NC} Operation abgeschlossen"
echo -e "${YELLOW}Warnung:${NC} Datei existiert bereits"
echo -e "${RED}Fehler:${NC} Datei nicht gefunden"
# Keine Farben, wenn NO_COLOR gesetzt ist (wird von einigen Tools unterstützt)
if [ -n "$NO_COLOR" ]; then
RED=""
GREEN=""
YELLOW=""
NC=""
fiEine der häufigsten Modifikationen ist das Hinzufügen von
Verzeichnissen zum PATH:
#!/usr/bin/env bash
# Neues Verzeichnis am Anfang des PATH hinzufügen (höhere Priorität)
export PATH="/usr/local/bin:$PATH"
# Neues Verzeichnis am Ende des PATH hinzufügen (niedrigere Priorität)
export PATH="$PATH:/home/user/bin"
# Mehrere Verzeichnisse hinzufügen
export PATH="/opt/tools/bin:/opt/tools/scripts:$PATH"
# Überpüfung, ob ein Verzeichnis bereits im PATH ist, bevor es hinzugefügt wird
if [[ ":$PATH:" != *":/home/user/bin:"* ]]; then
export PATH="$PATH:/home/user/bin"
fiManchmal möchten Sie eine Umgebungsvariable nur temporär ändern, ohne die ursprüngliche Einstellung zu verlieren:
#!/usr/bin/env bash
# Originalen PATH speichern
OLD_PATH="$PATH"
# PATH temporär ändern
export PATH="/spezial/bin:$PATH"
# Befehle ausführen mit dem modifizierten PATH
special_command
# Ursprünglichen PATH wiederherstellen
export PATH="$OLD_PATH"Sie können die Umgebung für einen Unterprozess ändern, ohne die aktuelle Shell zu beeinflussen:
# Nur für diesen Befehl
LANG=de_DE.UTF-8 date
# Für eine Gruppe von Befehlen
(
export LC_ALL=fr_FR.UTF-8
export TZ="Europe/Paris"
date
cal
)
# Die Hauptshell bleibt unverändert
dateUmgebungsvariablen, die in der Shell gesetzt werden, existieren nur während der aktuellen Shell-Sitzung. Um Umgebungsvariablen dauerhaft zu speichern, müssen sie in entsprechenden Konfigurationsdateien gesetzt werden. Die genaue Methode hängt von der verwendeten Shell und dem gewünschten Gültigkeitsbereich ab.
Für die Bash-Shell können folgende Dateien verwendet werden:
Nur für den aktuellen Benutzer:
~/.bashrc: Wird geladen für interaktive
Non-Login-Shells (z.B. bei Öffnen eines neuen Terminal-Fensters)~/.bash_profile, ~/.profile oder
~/.bash_login: Wird geladen für Login-Shells (z.B. bei der
Anmeldung am System)Beispiel für einen Eintrag in ~/.bashrc:
# Eigene Binärdateien zum PATH hinzufügen
export PATH="$HOME/bin:$PATH"
# Editor für verschiedene Programme festlegen
export EDITOR="vim"
# Eigene Alias-Definitionen
export JAVA_HOME="/usr/lib/jvm/java-11-openjdk"Systemweit für alle Benutzer:
/etc/profile: Wird für alle Benutzer bei Login-Shells
geladen/etc/bash.bashrc oder /etc/bashrc: Wird
für alle Benutzer bei interaktiven Shells geladen/etc/profile.d/: Werden
automatisch von /etc/profile eingebundenBeispiel für einen Eintrag in
/etc/profile.d/java.sh:
# Systemweite Java-Einstellungen
export JAVA_HOME=/usr/lib/jvm/default-java
export PATH="$JAVA_HOME/bin:$PATH"Organisieren Sie Ihre Umgebungsvariablen thematisch:
# In ~/.bashrc oder separaten Dateien in ~/.bashrc.d/
# Java-Entwicklungsumgebung
export JAVA_HOME="/usr/lib/jvm/java-11"
export MAVEN_HOME="/opt/maven"
# Python-Entwicklungsumgebung
export PYTHONPATH="$HOME/lib/python"
export VIRTUAL_ENV_DISABLE_PROMPT=1Verwenden Sie bedingte Einstellungen:
# Nur hinzufügen, wenn das Verzeichnis existiert
if [ -d "$HOME/bin" ]; then
export PATH="$HOME/bin:$PATH"
fi
# Bevorzugten Editor setzen
if command -v nvim &> /dev/null; then
export EDITOR="nvim"
elif command -v vim &> /dev/null; then
export EDITOR="vim"
else
export EDITOR="nano"
fiVermeiden Sie doppelte Einträge im PATH:
# Funktion zum sicheren Hinzufügen zum PATH
add_to_path() {
if [[ ":$PATH:" != *":$1:"* ]]; then
export PATH="$1:$PATH"
fi
}
# Anwendung
add_to_path "$HOME/bin"
add_to_path "/usr/local/go/bin"Es ist wichtig, die Unterschiede in der Behandlung von Umgebungsvariablen zwischen interaktiven Shells und Shell-Skripten zu verstehen:
Um dies zu verdeutlichen, betrachten wir folgendes Beispiel:
#!/usr/bin/env bash
# Dateiname: set_vars.sh
# Reguläre Variable
MY_VAR="im Skript definiert"
# Umgebungsvariable
export MY_ENV_VAR="im Skript exportiert"
echo "Im Skript: MY_VAR = $MY_VAR"
echo "Im Skript: MY_ENV_VAR = $MY_ENV_VAR"Wenn dieses Skript ausgeführt wird:
$ ./set_vars.sh
Im Skript: MY_VAR = im Skript definiert
Im Skript: MY_ENV_VAR = im Skript exportiert
$ echo $MY_VAR
# Keine Ausgabe, da die Variable nicht exportiert wurde
$ echo $MY_ENV_VAR
# Keine Ausgabe, da die Umgebungsvariable nur im Skript existierteUm Umgebungsvariablen von einem Skript an die aufrufende Shell zu übergeben, gibt es zwei Hauptmethoden:
Skript mit source oder .
ausführen:
$ source ./set_vars.sh
# oder
$ . ./set_vars.sh
$ echo $MY_VAR
im Skript definiert
$ echo $MY_ENV_VAR
im Skript exportiertAusgabe des Skripts in eval
auswerten:
# Ein Skript, das Shell-Befehle ausgibt
$ cat export_vars.sh
#!/usr/bin/env bash
echo "export RESULT_VAR='Berechnetes Ergebnis'"
# Ausführen und Auswerten
$ eval $(./export_vars.sh)
$ echo $RESULT_VAR
Berechnetes ErgebnisUmgebungsvariablen können Sicherheitsrisiken darstellen, wenn sie für sensible Daten verwendet werden:
keyring,
pass oder vault für AnmeldedatenBeispiel für sicherere Alternativen:
#!/usr/bin/env bash
# Unsicher: Passwort in Umgebungsvariable
export DB_PASSWORD="geheim123"
# Besser: Aus Datei mit beschränkten Berechtigungen lesen
DB_PASSWORD=$(cat ~/.secrets/db_password)
# Noch besser: Tool wie 'pass' verwenden
DB_PASSWORD=$(pass database/production)
# Anschließend Passwort nur kurzzeitig im Speicher halten und nicht exportieren
mysql -u admin -p"$DB_PASSWORD" database
DB_PASSWORD="" # Variable leeren nach VerwendungBei Problemen mit Umgebungsvariablen können folgende Techniken helfen:
Anzeigen aller Umgebungsvariablen:
env | sort
printenv | sortÜberprüfen einer bestimmten Variable:
echo "$PATH"
echo "${#PATH}" # Länge der Variable (nützlich bei sehr langen Werten)Prüfen, ob eine Variable exportiert wurde:
declare -p PATH # Zeigt Variablentyp und -wert anVerfolgen der Shell-Initialisierung:
# Bash mit Debugging-Option starten
bash -x
# Oder bestimmte Initialisierungsdatei mit Debugging ausführen
bash -x ~/.bashrcTemporäre Shell mit sauberer Umgebung:
# Startet eine neue Bash ohne Umgebungsvariablen zu erben
env -i bash --noprofile --norcZum Abschluss eine Gegenüberstellung der wichtigsten Eigenschaften:
| Aspekt | Shell-Variablen | Umgebungsvariablen |
|---|---|---|
| Deklaration | var=wert |
export var=wert oder
var=wert; export var |
| Sichtbarkeit | Nur in der aktuellen Shell | In der aktuellen Shell und allen Kindprozessen |
| Vererbung | Nicht an Kindprozesse vererbt | An Kindprozesse vererbt |
| Rückgabe | Nicht von Kindprozessen zurückgegeben | Nicht von Kindprozessen zurückgegeben |
| Anzeigen | echo "$var" |
echo "$var" oder printenv var |
| Persistenz | Nur während der Shell-Sitzung | Nur während der Shell-Sitzung, kann in Initialisierungsdateien gespeichert werden |
| Typische Verwendung | Temporäre Werte, Funktionsparameter | Konfiguration für Programme, systemweite Einstellungen |
Die richtige Verwendung von Shell-Variablen und Umgebungsvariablen ist ein wesentlicher Bestandteil effektiver Shell-Skripte. Durch das Verständnis ihrer Unterschiede und Einsatzbereiche können Sie robustere und wartbarere Skripte erstellen, die harmonisch mit der Shell-Umgebung und anderen Programmen interagieren.
Die Bash-Shell und andere Unix-Shells stellen eine Reihe von speziellen eingebauten Variablen bereit, die den Zugriff auf wichtige Informationen über die aktuelle Shell-Umgebung, den Ausführungskontext und die übergebenen Parameter ermöglichen. Diese Variablen sind leistungsstarke Werkzeuge für Shell-Skriptentwickler, da sie den Zugriff auf Laufzeitinformationen, die Steuerung des Programmflusses und die Implementierung von robusten Fehlerbehandlungsroutinen erleichtern.
Positionsparameter sind Variablen, die die an ein Skript oder eine Funktion übergebenen Argumente enthalten.
$0 -
SkriptnameDie Variable $0 enthält den Namen des Skripts oder der
Shell, wie es in der Befehlszeile aufgerufen wurde:
#!/usr/bin/env bash
echo "Dieses Skript heißt: $0"Ausgabe:
Dieses Skript heißt: ./mein_skript.sh
Im Kontext einer Funktion enthält $0 weiterhin den
Skriptnamen, nicht den Funktionsnamen.
$1,
$2, $3, … - PositionsparameterDiese Variablen enthalten die einzelnen Argumente, die dem Skript oder der Funktion übergeben wurden:
#!/usr/bin/env bash
echo "Erstes Argument: $1"
echo "Zweites Argument: $2"
echo "Drittes Argument: $3"Ausgabe bei Aufruf mit
./mein_skript.sh datei.txt 42 "Hallo Welt":
Erstes Argument: datei.txt
Zweites Argument: 42
Drittes Argument: Hallo Welt
Im Kontext einer Funktion beziehen sich diese Variablen auf die Argumente der Funktion, nicht auf die des Skripts.
$# - Anzahl
der ArgumenteDiese Variable enthält die Anzahl der an das Skript oder die Funktion übergebenen Argumente:
#!/usr/bin/env bash
echo "Anzahl der Argumente: $#"
function beispiel() {
echo "Anzahl der Funktionsargumente: $#"
}
beispiel 1 2 3 4Ausgabe bei Aufruf mit ./mein_skript.sh a b c:
Anzahl der Argumente: 3
Anzahl der Funktionsargumente: 4
$@ - Alle
Argumente als separate StringsDiese spezielle Variable repräsentiert alle übergebenen Argumente, wobei jedes Argument als separater String behandelt wird. Dies ist besonders nützlich, wenn Argumente mit Leerzeichen korrekt verarbeitet werden sollen:
#!/usr/bin/env bash
echo "Alle Argumente: $@"
# Iteration über alle Argumente
for arg in "$@"; do
echo "Argument: $arg"
doneAusgabe bei Aufruf mit
./mein_skript.sh "Erster Eintrag" Zweiter "Dritter Eintrag":
Alle Argumente: Erster Eintrag Zweiter Dritter Eintrag
Argument: Erster Eintrag
Argument: Zweiter
Argument: Dritter Eintrag
Die Anführungszeichen um "$@" sind entscheidend, um
Argumente mit Leerzeichen korrekt zu behandeln.
$* - Alle
Argumente als ein StringÄhnlich wie $@, aber behandelt alle Argumente als einen
einzelnen String, getrennt durch das erste Zeichen der Umgebungsvariable
IFS (standardmäßig ein Leerzeichen):
#!/usr/bin/env bash
echo "Alle Argumente als ein String: $*"
# Iteration über alle Wörter in $*
for word in $*; do
echo "Wort: $word"
done
# Mit Anführungszeichen
for item in "$*"; do
echo "Item: $item"
doneAusgabe bei Aufruf mit
./mein_skript.sh "Erster Eintrag" Zweiter "Dritter Eintrag":
Alle Argumente als ein String: Erster Eintrag Zweiter Dritter Eintrag
Wort: Erster
Wort: Eintrag
Wort: Zweiter
Wort: Dritter
Wort: Eintrag
Item: Erster Eintrag Zweiter Dritter Eintrag
Die Unterschiede zwischen $@ und $* sind
subtil, aber wichtig: - "$@" expandiert zu
"$1" "$2" "$3" ... (jedes Argument wird als separater
String behandelt) - "$*" expandiert zu
"$1 $2 $3 ..." (alle Argumente als ein einzelner String,
getrennt durch das erste Zeichen in IFS)
$? -
Exit-Status des letzten BefehlsDie Variable $? enthält den Exit-Status des zuletzt
ausgeführten Befehls oder der zuletzt ausgeführten Funktion. Ein Wert
von 0 bedeutet normalerweise Erfolg, während Werte ungleich 0 auf Fehler
hindeuten:
#!/usr/bin/env bash
grep "muster" datei.txt
if [ $? -eq 0 ]; then
echo "Muster gefunden"
else
echo "Muster nicht gefunden"
fi
# Besser: Direkte Verwendung im if-Statement
if grep "muster" datei.txt; then
echo "Muster gefunden"
else
echo "Muster nicht gefunden"
fiDie direkte Verwendung im if-Statement ist zu
bevorzugen, da sie kürzer und weniger fehleranfällig ist.
$$ - Process
ID der aktuellen ShellDie Variable $$ enthält die Prozess-ID (PID) der
aktuellen Shell-Instanz oder des Skripts:
#!/usr/bin/env bash
echo "Die PID dieses Skripts ist: $$"
# Nützlich für temporäre Dateien
TEMP_FILE="/tmp/temp_$$.txt"
echo "Temporäre Datei: $TEMP_FILE"Diese Variable wird häufig verwendet, um eindeutige temporäre Dateinamen zu erstellen und so Konflikte zu vermeiden.
$! - Process
ID des zuletzt gestarteten HintergrundprozessesDie Variable $! enthält die PID des zuletzt in den
Hintergrund gestarteten Prozesses:
#!/usr/bin/env bash
# Starten eines Hintergrundprozesses
sleep 100 &
echo "Die PID des Hintergrundprozesses ist: $!"
# Warten auf den Hintergrundprozess
SLEEP_PID=$!
echo "Warte auf Prozess $SLEEP_PID..."
wait $SLEEP_PID
echo "Prozess $SLEEP_PID beendet"Diese Variable ist besonders nützlich für die Überwachung und Steuerung von Hintergrundprozessen.
$- - Aktuelle
Shell-OptionenDie Variable $- enthält die aktuell aktivierten
Shell-Optionen:
#!/usr/bin/env bash
echo "Aktive Shell-Optionen: $-"
# Prüfen, ob die Shell im interaktiven Modus läuft
if [[ $- == *i* ]]; then
echo "Interaktive Shell"
else
echo "Nicht-interaktive Shell"
fiDie Shell-Optionen sind einzelne Buchstaben, die verschiedene Modi
repräsentieren, wie z.B. i für interaktiven Modus oder
x für den Debug-Modus (set -x).
$_ - Letztes
Argument des letzten BefehlsDie Variable $_ enthält das letzte Argument des zuletzt
ausgeführten Befehls:
#!/usr/bin/env bash
echo "Hallo Welt"
echo "Das letzte Argument war: $_" # Ausgabe: Das letzte Argument war: Welt
mkdir -p /tmp/test/dir
cd "$_" # Wechselt in den gerade erstellten Ordner
pwd # Ausgabe: /tmp/test/dirDiese Variable kann in interaktiven Shells nützlich sein, um schnell auf das letzte verwendete Argument zuzugreifen.
LINENO -
Aktuelle ZeilennummerDie Variable LINENO enthält die aktuelle Zeilennummer im
Skript:
#!/usr/bin/env bash
echo "Dies ist Zeile $LINENO"
function fehler_anzeigen() {
echo "Fehler in Zeile $LINENO"
}
fehler_anzeigenAusgabe:
Dies ist Zeile 2
Fehler in Zeile 5
FUNCNAME -
Name der aktuellen FunktionDie Variable FUNCNAME ist ein Array, das die aktuellen
Funktionsnamen in der Aufrufhierarchie enthält:
#!/usr/bin/env bash
function c() {
echo "In Funktion ${FUNCNAME[0]}, aufgerufen von ${FUNCNAME[1]}"
}
function b() {
c
}
function a() {
b
}
a
echo "Hauptskript: ${FUNCNAME[0]}"Ausgabe:
In Funktion c, aufgerufen von b
Hauptskript:
BASH_SOURCE -
SkriptdateipfadDie Variable BASH_SOURCE ist ein Array, das den Pfad zur
Quelldatei in der Aufrufhierarchie enthält:
#!/usr/bin/env bash
echo "Dieses Skript: ${BASH_SOURCE[0]}"
function get_script_dir() {
# Absoluter Pfad zum Skriptverzeichnis
echo "$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
}
SCRIPT_DIR=$(get_script_dir)
echo "Skriptverzeichnis: $SCRIPT_DIR"Diese Variable ist besonders nützlich, um den absoluten Pfad eines Skripts zu ermitteln, unabhängig davon, von wo es aufgerufen wurde.
BASH_LINENO -
Zeilennummern der AufrufhierarchieDie Variable BASH_LINENO ist ein Array, das die
Zeilennummern in der Aufrufhierarchie enthält:
#!/usr/bin/env bash
function trace() {
echo "Aufgerufen von Zeile ${BASH_LINENO[0]} in ${BASH_SOURCE[1]}"
}
function foo() {
trace
}
# Zeile 10
fooAusgabe:
Aufgerufen von Zeile 10 in ./mein_skript.sh
Die obigen Variablen können kombiniert werden, um robuste Fehlerdiagnose-Funktionen zu implementieren:
#!/usr/bin/env bash
# Funktion zur Fehlerbehandlung
error_handler() {
local line=$1
local command=$2
local code=$3
local source_file=${BASH_SOURCE[1]}
echo "Fehler in $source_file, Zeile $line" >&2
echo "Befehl: $command" >&2
echo "Exit-Status: $code" >&2
exit $code
}
# Trap für ERROR-Ereignis, ruft error_handler mit Zeilennummer, Befehl und Exit-Status auf
trap 'error_handler ${LINENO} "$BASH_COMMAND" $?' ERR
# Fehlerbehandlung aktivieren
set -e
# Beispiel für einen fehlerhaften Befehl
echo "Vor dem Fehler"
cat /nicht/existierende/datei
echo "Nach dem Fehler (wird nie erreicht)"Ausgabe:
Vor dem Fehler
cat: /nicht/existierende/datei: No such file or directory
Fehler in ./mein_skript.sh, Zeile 19
Befehl: cat /nicht/existierende/datei
Exit-Status: 1
Die speziellen Parameter-Variablen verhalten sich unterschiedlich in Funktionen und im Hauptskript:
#!/usr/bin/env bash
# Funktion, die ihre Argumente und die Skriptargumente anzeigt
function zeige_argumente() {
echo "--- Funktion: $FUNCNAME ---"
echo "Funktionsargumente ($#): $@"
echo "Erstes Funktionsargument: $1"
echo "Skriptname: $0"
# Um auf die Skriptargumente zuzugreifen, müssen diese separat übergeben werden
}
# Hauptteil des Skripts
echo "--- Hauptskript ---"
echo "Skriptargumente ($#): $@"
echo "Erstes Skriptargument: $1"
echo "Skriptname: $0"
# Funktion mit eigenen Argumenten aufrufen
zeige_argumente "Eins" "Zwei" "Drei"Ausgabe bei Aufruf mit ./mein_skript.sh A B C:
--- Hauptskript ---
Skriptargumente (3): A B C
Erstes Skriptargument: A
Skriptname: ./mein_skript.sh
--- Funktion: zeige_argumente ---
Funktionsargumente (3): Eins Zwei Drei
Erstes Funktionsargument: Eins
Skriptname: ./mein_skript.sh
Die Positionsparameter können mit dem shift-Befehl
manipuliert werden:
#!/usr/bin/env bash
echo "Alle Argumente: $@"
echo "Anzahl der Argumente: $#"
echo "Erstes Argument: $1"
# Entfernt das erste Argument und verschiebt alle anderen nach links
shift
echo "Nach shift: $@"
echo "Anzahl der Argumente: $#"
echo "Neues erstes Argument: $1"
# Mehrere Positionen auf einmal verschieben
shift 2
echo "Nach shift 2: $@"
echo "Anzahl der Argumente: $#"
echo "Neues erstes Argument: $1"Ausgabe bei Aufruf mit ./mein_skript.sh A B C D E:
Alle Argumente: A B C D E
Anzahl der Argumente: 5
Erstes Argument: A
Nach shift: B C D E
Anzahl der Argumente: 4
Neues erstes Argument: B
Nach shift 2: D E
Anzahl der Argumente: 2
Neues erstes Argument: D
Die shift-Operation ist besonders nützlich für die
Verarbeitung von Argumenten in einer Schleife oder für die
Implementierung einfacher Befehlszeilenparameter.
Die Positionsparameter können auch mit dem set-Befehl
gesetzt oder geändert werden:
#!/usr/bin/env bash
# Aktuelle Argumente anzeigen
echo "Ursprüngliche Argumente: $@"
# Neue Positionsparameter setzen
set -- "Neu1" "Neu2" "Neu3"
echo "Neue Argumente: $@"
# Vorsicht: set ohne -- setzt Shell-Optionen
set -x # Aktiviert Debug-Ausgabe
echo "Debug-Modus aktiviert"
set +x # Deaktiviert Debug-AusgabeDas Doppel-Minuszeichen (--) trennt Optionen von
Positionsparametern für den set-Befehl.
#!/usr/bin/env bash
# Beenden bei Fehlern
set -e
# Fehlerbehandlungsfunktion
error_exit() {
echo "Fehler: $1" >&2
exit 1
}
# Anwendung
command_that_might_fail || error_exit "Befehl ist fehlgeschlagen in Zeile $LINENO"
# Rückgabewerte prüfen
if [ $? -ne 0 ]; then
error_exit "Vorangegangener Befehl fehlgeschlagen"
fi#!/usr/bin/env bash
# Eindeutigen Dateinamen erstellen
TEMP_FILE="/tmp/tmp_$$.txt"
echo "Verwende temporäre Datei: $TEMP_FILE"
# Sicherstellen, dass die temporäre Datei gelöscht wird
trap "rm -f $TEMP_FILE" EXIT
# Datei verwenden
echo "Temporärer Inhalt" > "$TEMP_FILE"
cat "$TEMP_FILE"Die Verwendung von $$ stellt sicher, dass der Dateiname
für jede Skriptausführung eindeutig ist, und der
trap-Befehl sorgt dafür, dass die temporäre Datei beim
Beenden des Skripts gelöscht wird.
#!/usr/bin/env bash
# Werte summieren
sum=0
for num in "$@"; do
if [[ "$num" =~ ^[0-9]+$ ]]; then
sum=$((sum + num))
else
echo "Warnung: '$num' ist keine Zahl" >&2
fi
done
echo "Summe aller Zahlen: $sum"#!/usr/bin/env bash
# Starten mehrerer Hintergrundprozesse
echo "Starte Hintergrundprozesse..."
sleep 10 &
PID1=$!
sleep 15 &
PID2=$!
echo "Gestartet: PID $PID1 und $PID2"
# Warten auf den ersten Prozess
echo "Warte auf Prozess $PID1..."
wait $PID1
echo "Prozess $PID1 beendet."
# Überprüfen, ob der zweite Prozess noch läuft
if kill -0 $PID2 2>/dev/null; then
echo "Prozess $PID2 läuft noch. Beende ihn..."
kill $PID2
else
echo "Prozess $PID2 bereits beendet."
fi#!/usr/bin/env bash
# Funktion für Logging mit Zeilennummer
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [Zeile ${BASH_LINENO[0]}] $1"
}
# Anwendung
log "Skript gestartet"
echo "Führe Aufgabe aus..."
log "Aufgabe abgeschlossen"
function complex_task() {
log "Komplexe Aufgabe gestartet"
# Aufgabenlogik hier
log "Komplexe Aufgabe beendet"
}
complex_task
log "Skript beendet"Bei der Verwendung eingebauter Variablen in Shell-Skripten sollten folgende Punkte beachtet werden:
Zitierung beachten: Verwenden Sie immer Anführungszeichen um Variablen, um Probleme mit Leerzeichen und Sonderzeichen zu vermeiden:
# Gut
echo "Argumente: $@"
for arg in "$@"; do echo "$arg"; done
# Schlecht (kann bei Leerzeichen in Argumenten Probleme verursachen)
echo Argumente: $@
for arg in $@; do echo $arg; doneExit-Status sofort prüfen: Die Variable
$? enthält nur den Status des unmittelbar vorhergehenden
Befehls:
# Riskant, $? kann durch echo überschrieben werden
grep "muster" datei.txt
echo "Suche beendet"
if [ $? -ne 0 ]; then echo "Fehler"; fi
# Besser
if grep "muster" datei.txt; then
echo "Muster gefunden"
else
echo "Muster nicht gefunden"
fiZustand der Positionsparameter bewahren: Wenn
Sie shift verwenden oder Positionsparameter ändern, sichern
Sie vorher den ursprünglichen Zustand, falls Sie ihn später
benötigen:
# Original-Argumente sichern
original_args=("$@")
# Argumente verarbeiten
while [ $# -gt 0 ]; do
# Verarbeitung...
shift
done
# Original-Argumente wiederherstellen
set -- "${original_args[@]}"Portable Skripts: Für maximale Portabilität zwischen verschiedenen Shell-Implementierungen sollten Sie sich auf die POSIX-standardisierten Variablen beschränken und Shell-spezifische Funktionen vermeiden oder durch Alternativen absichern.
Die eingebauten Variablen der Bash und anderer Unix-Shells sind mächtige Werkzeuge, die den Zugriff auf wichtige Laufzeitinformationen, Kontextdaten und Parameter ermöglichen. Sie sind unerlässlich für die Entwicklung robuster und flexibler Shell-Skripte.
Die wichtigsten eingebauten Variablen im Überblick:
| Variable | Beschreibung |
|---|---|
$0 |
Skriptname |
$1, $2, … |
Positionsparameter (Argumente) |
$# |
Anzahl der Argumente |
$@ |
Alle Argumente als separate Strings |
$* |
Alle Argumente als ein String |
$? |
Exit-Status des letzten Befehls |
$$ |
PID der aktuellen Shell |
$! |
PID des letzten Hintergrundprozesses |
$- |
Aktuelle Shell-Optionen |
$_ |
Letztes Argument des letzten Befehls |
LINENO |
Aktuelle Zeilennummer |
FUNCNAME |
Array mit Funktionsnamen in der Aufrufhierarchie |
BASH_SOURCE |
Array mit Dateipfaden in der Aufrufhierarchie |
BASH_LINENO |
Array mit Zeilennummern in der Aufrufhierarchie |
Die effektive Nutzung dieser Variablen verbessert die Funktionalität, Wartbarkeit und Robustheit Ihrer Shell-Skripte erheblich und sollte Teil des Standardrepertoires jedes Shell-Skriptentwicklers sein.
Das Dienstprogramm xargs ist ein leistungsstarkes
Werkzeug in der Unix/Linux-Umgebung, das die Ausführung von Befehlen mit
Argumenten aus der Standardeingabe ermöglicht. Es dient als Brücke
zwischen Befehlen und ist besonders nützlich für die Verarbeitung großer
Datenmengen oder wenn die Anzahl der zu verarbeitenden Elemente die
Befehlszeilenlimit überschreiten würde.
xargs nimmt die Ausgabe eines Befehls aus der
Standardeingabe entgegen, teilt sie in einzelne Argumente auf und
verwendet diese als Parameter für einen anderen Befehl. Die grundlegende
Syntax lautet:
befehl1 | xargs [optionen] befehl2Hierbei: - befehl1 erzeugt eine Ausgabe, die an
xargs weitergeleitet wird - xargs konvertiert
diese Ausgabe in Befehlszeilenargumente - befehl2 wird mit
diesen Argumenten ausgeführt
Ein klassischer Anwendungsfall ist das Löschen von Dateien, die bestimmten Kriterien entsprechen:
# Alle temporären Dateien älter als 7 Tage finden und löschen
find /tmp -type f -mtime +7 | xargs rm -f# Alle .txt Dateien nach einem bestimmten Muster durchsuchen
cat filelist.txt | xargs grep "Muster"xargs bietet verschiedene Optionen, um sein Verhalten
anzupassen:
-n: Begrenzung
der Anzahl der Argumente pro Befehlsaufruf# Jede Datei einzeln verarbeiten (ein Argument pro Befehlsaufruf)
ls *.txt | xargs -n 1 wc -l
# Je zwei Dateien zusammen verarbeiten
ls *.txt | xargs -n 2 diff-p:
Interaktiver Modus mit Bestätigung# Bestätigung vor jeder Befehlsausführung anfordern
find . -name "*.tmp" | xargs -p rm-I:
Platzhalter für Argumenteinfügung definierenDiese Option ist besonders nützlich, um Argumente an bestimmten Stellen des Befehls einzufügen:
# Alle .jpg Dateien in einen anderen Ordner kopieren und umbenennen
find . -name "*.jpg" | xargs -I{} cp {} /zielpfad/{}_backup.jpg
# Für jede gefundene Datei eine individuelle Aktion ausführen
cat urls.txt | xargs -I{} curl -o {}.html {}-0 oder
--null: Nullbyte als Trennzeichen verwendenDiese Option ist wichtig für die sichere Verarbeitung von Dateinamen mit Leerzeichen oder Sonderzeichen:
# Sicherere Methode zum Löschen von Dateien mit Sonderzeichen im Namen
find . -name "*.log" -print0 | xargs -0 rmEine der stärksten Funktionen von xargs ist die
Fähigkeit, Befehle parallel auszuführen:
# Bis zu 4 Befehle gleichzeitig ausführen
find . -name "*.png" | xargs -P 4 -I{} convert {} {}.jpg
# Große Dateien parallel komprimieren
find . -size +10M | xargs -P $(nproc) -I{} gzip {}Die Option -P gefolgt von einer Zahl gibt die maximale
Anzahl paralleler Prozesse an. Mit $(nproc) kann die Anzahl
der verfügbaren Prozessorkerne automatisch ermittelt werden.
xargs lässt sich effektiv mit anderen Unix-Werkzeugen
kombinieren:
# Alle .md Dateien finden, die das Wort "TODO" enthalten
find . -name "*.md" | xargs grep -l "TODO"
# Alle Prozesse eines bestimmten Benutzers beenden
ps -u username | grep processname | awk '{print $1}' | xargs kill# Dateien basierend auf ihrem Inhalt in verschiedene Ordner verschieben
find . -name "*.log" | xargs -I{} sh -c 'grep -q "ERROR" {} && mv {} ./errors/ || mv {} ./success/'# Benutzer mit Heimatverzeichnissen auflisten und verarbeiten
cat /etc/passwd | cut -d: -f1,6 | xargs -L 1 echo "User:"Die Option -L 1 verarbeitet jeweils eine Zeile als
Argument, unabhängig von Leerzeichen.
Beim Einsatz von xargs sind einige Aspekte zu
beachten:
Sonderzeichen in Dateinamen: Ohne die
-0-Option können Dateinamen mit Leerzeichen,
Zeilenumbrüchen oder anderen Sonderzeichen zu Problemen führen.
# Richtig (sicher):
find . -name "*.txt" -print0 | xargs -0 rm
# Potenziell gefährlich:
find . -name "*.txt" | xargs rmBefehlszeilenlimit: Obwohl xargs
entwickelt wurde, um das Argument-Limit der Shell zu umgehen, haben
Befehle immer noch ein maximales Limit für die
Befehlszeilenlänge.
Exit-Status: xargs gibt den
Exit-Status des letzten ausgeführten Befehls zurück, was bei der
Fehlerbehandlung zu beachten ist.
Verwenden Sie -print0 und -0
für sichere Dateinamenverarbeitung:
find . -type f -name "*.tmp" -print0 | xargs -0 rmPrüfen Sie gefährliche Operationen im Voraus mit
-p:
find /pfad -name "alte_dateien*" | xargs -p rmNutzen Sie -I{} für lesbaren und flexiblen
Code:
find . -name "*.jpg" | xargs -I{} sh -c 'echo "Verarbeite {}"; convert {} {}.png'Begrenzen Sie die Argumentanzahl bei ressourcenintensiven Befehlen:
find . -name "*.mp4" | xargs -n 5 ffmpeg-convertNutzen Sie parallele Verarbeitung für Performance-Gewinn:
find . -name "*.csv" | xargs -P 8 -I{} python process_data.py {}xargs lässt sich nahtlos in Shell-Skripte integrieren,
um wiederkehrende Aufgaben zu automatisieren:
#!/usr/bin/env bash
# Skript zum Batch-Verarbeiten von Bildern
usage() {
echo "Usage: $0 [-r] [-s size] [directory]"
echo " -r Rekursive Verarbeitung"
echo " -s size Zielgröße (z.B. 800x600)"
exit 1
}
size="800x600"
recursive=""
while getopts "rs:" opt; do
case $opt in
r) recursive="-r" ;;
s) size="$OPTARG" ;;
*) usage ;;
esac
done
shift $((OPTIND-1))
directory="${1:-.}" # Standardmäßig aktuelles Verzeichnis, wenn nicht angegeben
# Bilder finden und mit bis zu 4 parallelen Prozessen verarbeiten
find "$directory" $recursive -type f -name "*.jpg" -print0 | \
xargs -0 -P 4 -I{} sh -c 'echo "Verarbeite {}"; convert {} -resize '"$size"' {}.resized.jpg'
echo "Verarbeitung abgeschlossen."Es ist wichtig, den Unterschied zwischen xargs und
Befehlssubstitution ($() oder Backticks) zu verstehen:
# Befehlssubstitution: Gesamte Ausgabe wird als ein String behandelt
rm $(find . -name "*.tmp")
# xargs: Kann die Ausgabe in mehrere Aufrufe aufteilen
find . -name "*.tmp" | xargs rmDie Befehlssubstitution funktioniert gut für kleine Datenmengen, kann
aber bei vielen Argumenten das Befehlszeilenlimit überschreiten.
xargs umgeht dieses Problem, indem es den Befehl bei Bedarf
mehrmals mit Teilmengen der Argumente ausführt.
Die Verarbeitung von Befehlszeilenargumenten ist ein zentraler Aspekt
professioneller Shell-Skripte. Mit zunehmendem Funktionsumfang eines
Skripts wächst oft auch die Komplexität der Befehlszeilenschnittstelle.
Das getopt-Werkzeug bietet einen robusten Mechanismus zur
Verarbeitung von Befehlszeilenoptionen und Argumenten, der weit über die
einfachen eingebauten Möglichkeiten der Bash hinausgeht.
getopt ist ein externes Kommandozeilenprogramm, das
Befehlszeilenargumente nach dem POSIX-Standard analysiert und neu
formatiert. Es stellt eine zuverlässige und standardisierte Methode zur
Verfügung, um:
-a)--all)-f datei.txt oder
--file=datei.txt)-abc statt
-a -b -c)zu verarbeiten.
Es ist wichtig, zwischen dem Shell-eigenen
getopts-Befehl und dem externen
getopt-Werkzeug zu unterscheiden:
| Merkmal | getopts (Shell-Builtin) | getopt (externes Programm) |
|---|---|---|
| Verfügbarkeit | In allen POSIX-Shells verfügbar | Muss möglicherweise separat installiert werden |
| Lange Optionen | Nicht unterstützt | Unterstützt (in der erweiterten Version) |
| Portabilität | Sehr gut (POSIX-konform) | Unterschiedlich je nach Version und System |
| Komplexität | Einfach zu verwenden | Komplexere Syntax, aber mächtiger |
| Argumente | Weniger flexibel | Flexibler, unterstützt erweiterte Argumentformate |
In diesem Abschnitt konzentrieren wir uns auf das externe
getopt-Werkzeug, da es umfangreichere Funktionen bietet,
insbesondere für komplexere Skripte.
Die allgemeine Syntax für die Verwendung von getopt in
Skripten ist:
OPTIONS=$(getopt -o kurze_optionen -l lange_optionen -- "$@")Wobei: - -o kurze_optionen die unterstützten kurzen
Optionen angibt - -l lange_optionen die unterstützten
langen Optionen angibt - -- signalisiert das Ende der
Optionen für getopt selbst - "$@" alle
Befehlszeilenargumente übergibt, die an das Skript übergeben wurden
Ein einfaches Beispiel:
#!/usr/bin/env bash
# Überprüfen, ob die erweiterte getopt-Version verfügbar ist
if ! getopt --test > /dev/null; then
echo "Erweiterte getopt-Version wird benötigt"
exit 1
fi
# Optionen definieren
OPTIONS=v,h,f:,d::
LONGOPTIONS=verbose,help,file:,debug::
# Optionen analysieren
PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTIONS --name "$0" -- "$@")
# Fehlerbehandlung für ungültige Optionen
if [[ $? -ne 0 ]]; then
# getopt hat einen Fehler gemeldet
exit 2
fi
# Analysierte Optionen in die Positionsparameter einsetzen
eval set -- "$PARSED"
# Standardwerte setzen
VERBOSE=false
FILE=""
DEBUG=0
# Optionen verarbeiten
while true; do
case "$1" in
-v|--verbose)
VERBOSE=true
shift
;;
-h|--help)
echo "Verwendung: $0 [Optionen]"
echo "Optionen:"
echo " -v, --verbose Ausführliche Ausgabe aktivieren"
echo " -h, --help Diese Hilfe anzeigen"
echo " -f, --file=DATEI Zu verarbeitende Datei angeben"
echo " -d, --debug[=LVL] Debug-Modus aktivieren (optional mit Level)"
exit 0
;;
-f|--file)
FILE="$2"
shift 2
;;
-d|--debug)
case "$2" in
"")
DEBUG=1
shift 2
;;
*)
DEBUG="$2"
shift 2
;;
esac
;;
--)
shift
break
;;
*)
echo "Programmierungsfehler"
exit 3
;;
esac
done
# Verarbeitung der verbleibenden positionale Parameter
echo "Verbose-Modus: $VERBOSE"
echo "Datei: $FILE"
echo "Debug-Level: $DEBUG"
echo "Übrige Parameter: $@"Die Syntax für die Definition von Optionen in getopt
folgt bestimmten Konventionen:
o # Option -o ohne Argument
o: # Option -o mit erforderlichem Argument
o:: # Option -o mit optionalem Argument
option # Option --option ohne Argument
option: # Option --option mit erforderlichem Argument
option:: # Option --option mit optionalem Argument
Beispiel für eine Definition:
OPTIONEN="hvo:f:"
LANGOPTIONEN="help,verbose,output:,file:"Diese Definition unterstützt: - -h oder
--help (kein Argument) - -v oder
--verbose (kein Argument) - -o argument oder
--output argument oder --output=argument
(erforderliches Argument) - -f datei oder
--file datei oder --file=datei (erforderliches
Argument)
Optionale Argumente (mit :: gekennzeichnet) werden
unterschiedlich gehandhabt, je nachdem ob kurze oder lange Optionen
verwendet werden:
-d5 (Argument direkt nach der
Option, ohne Leerzeichen)--debug=5 (Argument mit
Gleichheitszeichen verbunden)Beispiel:
#!/usr/bin/env bash
OPTIONS=d::,v
LONGOPTIONS=debug::,verbose
PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTIONS --name "$0" -- "$@")
eval set -- "$PARSED"
DEBUG=0
VERBOSE=false
while true; do
case "$1" in
-d|--debug)
case "$2" in
"")
DEBUG=1 # Standardwert, wenn kein Argument angegeben
shift 2
;;
*)
DEBUG="$2"
shift 2
;;
esac
;;
-v|--verbose)
VERBOSE=true
shift
;;
--)
shift
break
;;
esac
done
echo "Debug-Level: $DEBUG"
echo "Verbose: $VERBOSE"Manchmal möchten Sie eine Option mehrmals angeben, um mehrere Werte
zu sammeln (z.B. -i datei1 -i datei2). Dafür können Arrays
verwendet werden:
#!/usr/bin/env bash
OPTIONS=i:,o:
LONGOPTIONS=input:,output:
PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTIONS --name "$0" -- "$@")
eval set -- "$PARSED"
# Arrays für mehrere Eingabe- und Ausgabedateien
INPUT_FILES=()
OUTPUT_FILE=""
while true; do
case "$1" in
-i|--input)
INPUT_FILES+=("$2")
shift 2
;;
-o|--output)
OUTPUT_FILE="$2"
shift 2
;;
--)
shift
break
;;
esac
done
echo "Eingabedateien: ${INPUT_FILES[*]}"
echo "Ausgabedatei: $OUTPUT_FILE"Aufruf:
./script.sh -i datei1.txt -i datei2.txt -o ausgabe.txtRobuste Skripte sollten die übergebenen Argumente validieren, um Laufzeitfehler zu vermeiden:
#!/usr/bin/env bash
OPTIONS=f:,n:
LONGOPTIONS=file:,number:
PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTIONS --name "$0" -- "$@")
eval set -- "$PARSED"
FILE=""
NUMBER=0
while true; do
case "$1" in
-f|--file)
FILE="$2"
# Prüfen, ob die Datei existiert und lesbar ist
if [[ ! -r "$FILE" ]]; then
echo "Fehler: Datei '$FILE' existiert nicht oder ist nicht lesbar." >&2
exit 1
fi
shift 2
;;
-n|--number)
NUMBER="$2"
# Prüfen, ob es sich um eine ganze Zahl handelt
if ! [[ "$NUMBER" =~ ^[0-9]+$ ]]; then
echo "Fehler: '$NUMBER' ist keine gültige Zahl." >&2
exit 1
fi
shift 2
;;
--)
shift
break
;;
esac
done
echo "Datei: $FILE"
echo "Zahl: $NUMBER"Viele moderne Kommandozeilenprogramme verwenden ein
Unterkommando-Muster (wie bei git, docker
usw.). Mit getopt können Sie dieses Muster
implementieren:
#!/usr/bin/env bash
# Globale Optionen
GLOBAL_OPTIONS=v,h
GLOBAL_LONGOPTIONS=verbose,help
# Standardwerte
VERBOSE=false
# Funktion für die Hilfe
show_help() {
cat << EOF
Verwendung: $0 [globale_optionen] <befehl> [befehl_optionen]
Globale Optionen:
-v, --verbose Ausführliche Ausgabe aktivieren
-h, --help Diese Hilfe anzeigen
Befehle:
create Erstellt eine neue Ressource
delete Löscht eine Ressource
list Listet alle Ressourcen auf
Für Hilfe zu bestimmten Befehlen: $0 <befehl> --help
EOF
exit 0
}
# Funktion für create-Befehl
cmd_create() {
local OPTIONS=n:,t:,h
local LONGOPTIONS=name:,type:,help
local PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTIONS --name "$0 create" -- "$@")
eval set -- "$PARSED"
local NAME=""
local TYPE="default"
while true; do
case "$1" in
-n|--name)
NAME="$2"
shift 2
;;
-t|--type)
TYPE="$2"
shift 2
;;
-h|--help)
echo "Verwendung: $0 create [optionen]"
echo "Optionen:"
echo " -n, --name=NAME Name der zu erstellenden Ressource"
echo " -t, --type=TYPE Typ der Ressource (Standard: default)"
echo " -h, --help Diese Hilfe anzeigen"
exit 0
;;
--)
shift
break
;;
esac
done
if [[ -z "$NAME" ]]; then
echo "Fehler: Name muss angegeben werden" >&2
exit 1
fi
[[ "$VERBOSE" == true ]] && echo "Erstelle Ressource vom Typ '$TYPE'..."
echo "Ressource '$NAME' wurde erstellt."
}
# Funktion für delete-Befehl
cmd_delete() {
local OPTIONS=i:,f,h
local LONGOPTIONS=id:,force,help
local PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTIONS --name "$0 delete" -- "$@")
eval set -- "$PARSED"
local ID=""
local FORCE=false
while true; do
case "$1" in
-i|--id)
ID="$2"
shift 2
;;
-f|--force)
FORCE=true
shift
;;
-h|--help)
echo "Verwendung: $0 delete [optionen]"
echo "Optionen:"
echo " -i, --id=ID ID der zu löschenden Ressource"
echo " -f, --force Ohne Bestätigung löschen"
echo " -h, --help Diese Hilfe anzeigen"
exit 0
;;
--)
shift
break
;;
esac
done
if [[ -z "$ID" ]]; then
echo "Fehler: ID muss angegeben werden" >&2
exit 1
fi
if [[ "$FORCE" != true ]]; then
read -p "Möchten Sie die Ressource '$ID' wirklich löschen? [j/N] " CONFIRM
if [[ ! "$CONFIRM" =~ ^[jJ]$ ]]; then
echo "Löschvorgang abgebrochen."
exit 0
fi
fi
[[ "$VERBOSE" == true ]] && echo "Lösche Ressource..."
echo "Ressource '$ID' wurde gelöscht."
}
# Funktion für list-Befehl
cmd_list() {
local OPTIONS=t:,l,h
local LONGOPTIONS=type:,long,help
local PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTIONS --name "$0 list" -- "$@")
eval set -- "$PARSED"
local TYPE=""
local LONG=false
while true; do
case "$1" in
-t|--type)
TYPE="$2"
shift 2
;;
-l|--long)
LONG=true
shift
;;
-h|--help)
echo "Verwendung: $0 list [optionen]"
echo "Optionen:"
echo " -t, --type=TYPE Nur Ressourcen dieses Typs anzeigen"
echo " -l, --long Ausführliches Format verwenden"
echo " -h, --help Diese Hilfe anzeigen"
exit 0
;;
--)
shift
break
;;
esac
done
[[ "$VERBOSE" == true ]] && echo "Ressourcen werden abgerufen..."
if [[ "$LONG" == true ]]; then
echo "ID | Name | Typ | Erstelldatum"
echo "------------|-------------|-------------|-------------"
echo "res-001 | Beispiel 1 | Standard | 2023-05-01"
echo "res-002 | Beispiel 2 | Premium | 2023-05-02"
else
echo "res-001 Beispiel 1"
echo "res-002 Beispiel 2"
fi
}
# Globale Optionen verarbeiten
PARSED=$(getopt --options=$GLOBAL_OPTIONS --longoptions=$GLOBAL_LONGOPTIONS --name "$0" -- "$@")
if [[ $? -ne 0 ]]; then
exit 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-v|--verbose)
VERBOSE=true
shift
;;
-h|--help)
show_help
;;
--)
shift
break
;;
esac
done
# Unterkommando verarbeiten
COMMAND="$1"
shift
case "$COMMAND" in
create)
cmd_create "$@"
;;
delete)
cmd_delete "$@"
;;
list)
cmd_list "$@"
;;
"")
echo "Fehler: Kein Befehl angegeben" >&2
show_help
;;
*)
echo "Fehler: Unbekannter Befehl '$COMMAND'" >&2
show_help
;;
esacDie erweiterte Version von getopt (die lange Optionen
unterstützt) ist nicht auf allen Unix-Systemen standardmäßig verfügbar.
Besonders auf älteren oder eingeschränkten Systemen kann dies zu
Problemen führen. Für maximale Portabilität:
Prüfen Sie die Verfügbarkeit der erweiterten Version:
if ! getopt --test > /dev/null; then
echo "Die erweiterte getopt-Version wird benötigt, aber nicht gefunden."
echo "Auf Debian/Ubuntu: apt-get install util-linux"
echo "Auf CentOS/RHEL: yum install util-linux-ng"
exit 1
fiFallback-Mechanismus implementieren:
# Versuchen, erweiterte getopt-Version zu verwenden
if getopt --test > /dev/null 2>&1; then
# Erweiterte Version verfügbar
PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTIONS --name "$0" -- "$@")
if [ $? -ne 0 ]; then
exit 1
fi
eval set -- "$PARSED"
# Optionen verarbeiten...
else
# Einfache Version oder manuelle Verarbeitung als Fallback
# Hier nur kurze Optionen verarbeiten
while getopts "hvo:f:" opt; do
case $opt in
h) show_help ;;
v) VERBOSE=true ;;
o) OUTPUT="$OPTARG" ;;
f) FILE="$OPTARG" ;;
\?) exit 1 ;;
esac
done
shift $((OPTIND-1))
fiFür umfangreiche Skripte empfiehlt sich eine modulare Struktur mit separaten Funktionen für die Argumentverarbeitung und die eigentliche Funktionalität:
#!/usr/bin/env bash
# Funktion zur Verarbeitung der Befehlszeilenargumente
parse_arguments() {
local options="hv,o:,f:"
local longoptions="help,verbose,output:,file:"
local parsed=$(getopt --options=$options --longoptions=$longoptions --name "$0" -- "$@")
if [[ $? -ne 0 ]]; then
exit 1
fi
eval set -- "$parsed"
while true; do
case "$1" in
-h|--help)
show_help
exit 0
;;
-v|--verbose)
VERBOSE=true
shift
;;
-o|--output)
OUTPUT_FILE="$2"
shift 2
;;
-f|--file)
INPUT_FILE="$2"
shift 2
;;
--)
shift
break
;;
esac
done
# Restliche Argumente
ARGS=("$@")
}
# Funktion zur Validierung der Argumente
validate_arguments() {
if [[ -z "$INPUT_FILE" ]]; then
echo "Fehler: Eingabedatei muss angegeben werden (-f, --file)" >&2
exit 1
fi
if [[ ! -r "$INPUT_FILE" ]]; then
echo "Fehler: Eingabedatei '$INPUT_FILE' existiert nicht oder ist nicht lesbar" >&2
exit 1
fi
if [[ -n "$OUTPUT_FILE" && -e "$OUTPUT_FILE" ]]; then
read -p "Ausgabedatei '$OUTPUT_FILE' existiert bereits. Überschreiben? [j/N] " CONFIRM
if [[ ! "$CONFIRM" =~ ^[jJ]$ ]]; then
echo "Operation abgebrochen."
exit 0
fi
fi
}
# Funktion zur Anzeige der Hilfe
show_help() {
cat << EOF
Verwendung: $0 [optionen] [argumente]
Optionen:
-h, --help Diese Hilfe anzeigen
-v, --verbose Ausführliche Ausgabe aktivieren
-o, --output=DATEI Ausgabedatei angeben
-f, --file=DATEI Eingabedatei angeben (erforderlich)
Beispiele:
$0 -f input.txt -o output.txt
$0 --verbose --file=input.txt
EOF
}
# Hauptfunktion
main() {
# Standardwerte setzen
VERBOSE=false
INPUT_FILE=""
OUTPUT_FILE=""
ARGS=()
# Befehlszeilenargumente verarbeiten
parse_arguments "$@"
# Argumente validieren
validate_arguments
# Eigentliche Funktionalität ausführen
if [[ "$VERBOSE" == true ]]; then
echo "Verarbeite Datei: $INPUT_FILE"
[[ -n "$OUTPUT_FILE" ]] && echo "Ausgabe in: $OUTPUT_FILE"
fi
# Hier die eigentliche Verarbeitung durchführen
# ...
if [[ "$VERBOSE" == true ]]; then
echo "Verarbeitung abgeschlossen."
fi
}
# Skript starten
main "$@"--help)getoptgetopt-Version verfügbar
ist-v für
verbose, -h für help)Effektive Shell-Skripte müssen mit dem Benutzer kommunizieren können – sei es durch die Ausgabe von Informationen oder das Einlesen von Benutzereingaben. Die Bash bietet dafür verschiedene Kommandos und Techniken, die wir in diesem Abschnitt detailliert betrachten werden.
echoDer echo-Befehl ist der einfachste Weg, Text auf der
Standardausgabe (stdout) auszugeben. Seine Syntax ist überaus
einfach:
echo [OPTIONEN] [STRING]echo "Hallo Welt"Standardmäßig fügt echo einen Zeilenumbruch am Ende der
Ausgabe hinzu. Um dies zu verhindern, kann die Option -n
verwendet werden:
echo -n "Geben Sie Ihren Namen ein: "Die wichtigsten Optionen für echo sind:
-n: Unterdrückt den abschließenden Zeilenumbruch-e: Aktiviert die Interpretation von
Escape-Sequenzen-E: Deaktiviert die Interpretation von Escape-Sequenzen
(Standard)Mit der Option -e können verschiedene Escape-Sequenzen
interpretiert werden:
echo -e "Hallo\nWelt" # Gibt "Hallo" und "Welt" auf separaten Zeilen ausWichtige Escape-Sequenzen:
| Sequenz | Bedeutung |
|---|---|
\n |
Zeilenumbruch |
\t |
Tabulator |
\v |
Vertikaler Tabulator |
\r |
Wagenrücklauf |
\b |
Backspace |
\a |
Akustisches Signal (Bell) |
\\ |
Backslash |
\e |
Escape-Zeichen |
\033 |
ASCII-Zeichen in Oktalnotation |
\xHH |
ASCII-Zeichen in Hexadezimalnotation |
Mit ANSI-Escape-Sequenzen lassen sich farbige Ausgaben erzeugen:
echo -e "\033[31mDieser Text ist rot\033[0m"
echo -e "\033[1;33mFettgedruckter gelber Text\033[0m"Eine praktische Alternative ist, Variablen für die Farben zu definieren:
# Farbdefinitionen
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${RED}Fehler:${NC} Die Datei wurde nicht gefunden."
echo -e "${GREEN}Erfolg:${NC} Die Operation wurde abgeschlossen."
echo -e "${YELLOW}Warnung:${NC} Niedrige Festplattenkapazität."printfFür komplexere Formatierungen bietet die Bash den aus der
C-Programmierung bekannten printf-Befehl:
printf [FORMAT] [ARGUMENTE]Im Gegensatz zu echo fügt printf
standardmäßig keinen Zeilenumbruch am Ende hinzu:
printf "Hallo Welt"
printf "Hallo Welt\n" # Mit Zeilenumbruchprintf verwendet Formatspezifizierer, die mit
% beginnen:
printf "Der Wert ist: %d\n" 42
printf "Pi ist ungefähr %.2f\n" 3.14159Gebräuchliche Formatspezifizierer:
| Spezifizierer | Bedeutung |
|---|---|
%d, %i |
Dezimale Ganzzahl |
%f |
Fließkommazahl |
%s |
String |
%c |
Einzelnes Zeichen |
%x, %X |
Hexadezimalzahl (klein-/großgeschrieben) |
%o |
Oktalzahl |
%b |
Wie echo -e (interpretiert Escape-Sequenzen) |
%% |
Literales Prozentzeichen |
Formatspezifizierer können modifiziert werden:
printf "%10s\n" "Text" # Rechtsbündig mit Breite 10
printf "%-10s\n" "Text" # Linksbündig mit Breite 10
printf "%03d\n" 7 # Mit führenden Nullen auf 3 Stellen auffüllen
printf "%10.2f\n" 123.4567 # 10 Stellen breit, 2 NachkommastellenMit printf lassen sich leicht tabellarische Ausgaben
erzeugen:
printf "%-20s %-10s %8s\n" "Name" "Position" "Gehalt"
printf "%-20s %-10s %8.2f\n" "Max Mustermann" "Entwickler" 3500.00
printf "%-20s %-10s %8.2f\n" "Erika Musterfrau" "CTO" 5200.50
printf "%-20s %-10s %8.2f\n" "John Doe" "Admin" 3200.75Dies erzeugt eine formatierte Tabelle mit ausgerichteten Spalten.
echo vs. printf| Aspekt | echo |
printf |
|---|---|---|
| Zeilenumbruch | Automatisch (außer mit -n) |
Muss explizit mit \n angegeben werden |
| Escape-Sequenzen | Nur mit -e |
Immer aktiviert |
| Formatierung | Begrenzt | Umfangreiche Formatierungsoptionen |
| Typprüfung | Keine | Formatspezifizierer erzwingen bestimmte Typen |
| Verwendungsart | Einfache Ausgaben | Komplexe, formatierte Ausgaben |
readDer read-Befehl ermöglicht es, Benutzereingaben zu
erfassen und in Variablen zu speichern:
read [OPTIONEN] [VARIABLEN]echo "Wie lautet Ihr Name?"
read name
echo "Hallo, $name!"Mehrere Variablen können gleichzeitig gelesen werden:
echo "Geben Sie Vorname und Nachname ein:"
read vorname nachname
echo "Hallo, $vorname $nachname!"read teilt die Eingabe anhand von Leerzeichen auf. Die
letzte Variable erhält alle verbleibenden Wörter.
readDie wichtigsten Optionen für read sind:
-p PROMPT: Zeigt einen Prompt an, ohne eine separate
echo-Anweisung-s: Silent-Modus (keine Anzeige der Eingabe, nützlich
für Passwörter)-n NUM: Liest nur NUM Zeichen ein-t TIMEOUT: Beendet den Lesevorgang nach TIMEOUT
Sekunden-a ARRAY: Speichert die Eingabe in einem Array-r: Raw-Modus (interpretiert Backslashes nicht als
Escape-Zeichen)-d DELIMITER: Verwendet DELIMITER anstelle einer
Newline als EingabeendeEinfacher Dialog:
read -p "Bitte geben Sie Ihren Namen ein: " name
echo "Hallo, $name! Willkommen zum Bash-Scripting."Passwortabfrage:
read -sp "Passwort eingeben: " passwort
echo -e "\nDas eingegebene Passwort ist $passwort"Zeitbegrenzung:
read -t 5 -p "Sie haben 5 Sekunden Zeit zur Eingabe: " antwort
if [ -z "$antwort" ]; then
echo -e "\nZeitüberschreitung!"
else
echo -e "\nIhre Antwort: $antwort"
fiBegrenzung der Zeichenanzahl:
read -n 1 -p "Drücken Sie eine Taste, um fortzufahren [y/n]: " taste
echo
case "$taste" in
y|Y) echo "Fortfahren..." ;;
n|N) echo "Abbruch." ;;
*) echo "Ungültige Eingabe." ;;
esacEinlesen in ein Array:
read -p "Geben Sie eine Liste von Farben ein (durch Leerzeichen getrennt): " -a farben
echo "Anzahl der Farben: ${#farben[@]}"
echo "Die erste Farbe ist: ${farben[0]}"
echo "Alle Farben: ${farben[@]}"Mit read eingelesene Daten sollten validiert werden:
while true; do
read -p "Bitte geben Sie Ihr Alter ein: " alter
if [[ "$alter" =~ ^[0-9]+$ ]] && [ "$alter" -gt 0 ] && [ "$alter" -lt 120 ]; then
echo "Danke, Ihr Alter ($alter) wurde registriert."
break
else
echo "Bitte geben Sie ein gültiges Alter ein (1-119)."
fi
doneread kann auch Daten aus Dateien lesen, besonders
nützlich in Kombination mit Schleifen:
while IFS=: read -r username password uid gid info home shell; do
echo "Benutzer: $username, UID: $uid, Home: $home"
done < /etc/passwdErklärung: - IFS=: setzt das Trennzeichen auf einen
Doppelpunkt - -r verhindert, dass Backslashes als
Escape-Zeichen interpretiert werden - < /etc/passwd
leitet den Inhalt der Datei an die Schleife weiter
logfile="/var/log/syslog"
if [ -f "$logfile" ]; then
echo "Die letzten 5 Fehler im Logfile:"
grep "error" "$logfile" | head -5 | while read -r line; do
echo " - $line"
done
else
echo "Logfile $logfile nicht gefunden."
fiAlternativ können Eingaben direkt aus Strings gelesen werden:
# Here-String
read -r first_line <<< "Dies ist ein Beispieltext"
echo "$first_line"
# Here-Document
while read -r line; do
echo "Zeile: $line"
done << EOF
Erste Zeile
Zweite Zeile
Dritte Zeile
EOFHier ist ein vollständiges Beispiel für ein interaktives Konfigurationsskript:
#!/bin/bash
# Farbdefinitionen
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Konfigurationsdatei
config_file="$HOME/.myapp_config"
# Standardwerte
default_host="localhost"
default_port=8080
default_user="admin"
# Funktion zur Validierung der Port-Nummer
validate_port() {
if [[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 1 ] && [ "$1" -le 65535 ]; then
return 0
else
return 1
fi
}
# Willkommensnachricht
printf "${BLUE}%s${NC}\n" "Konfigurations-Assistent"
printf "%s\n" "========================================="
# Hostname
read -p "$(printf "Hostname [${GREEN}%s${NC}]: " "$default_host")" host
host=${host:-$default_host}
printf "Hostname: ${GREEN}%s${NC}\n\n" "$host"
# Port
while true; do
read -p "$(printf "Port [${GREEN}%d${NC}]: " "$default_port")" port
port=${port:-$default_port}
if validate_port "$port"; then
printf "Port: ${GREEN}%d${NC}\n\n" "$port"
break
else
printf "${RED}Fehler:${NC} Ungültiger Port. Bitte geben Sie eine Zahl zwischen 1 und 65535 ein.\n"
fi
done
# Benutzername
read -p "$(printf "Benutzername [${GREEN}%s${NC}]: " "$default_user")" user
user=${user:-$default_user}
printf "Benutzername: ${GREEN}%s${NC}\n\n" "$user"
# Passwort
read -sp "Passwort: " password
echo
if [ -z "$password" ]; then
printf "${YELLOW}Warnung:${NC} Kein Passwort angegeben.\n\n"
else
printf "${GREEN}Passwort festgelegt.${NC}\n\n"
fi
# Zusammenfassung
printf "${BLUE}%s${NC}\n" "Zusammenfassung der Konfiguration"
printf "%s\n" "========================================="
printf "%-15s: %s\n" "Hostname" "$host"
printf "%-15s: %d\n" "Port" "$port"
printf "%-15s: %s\n" "Benutzername" "$user"
printf "%-15s: %s\n" "Passwort" "${password:-(nicht angegeben)}"
printf "%s\n" "========================================="
# Bestätigung
read -n 1 -p "$(printf "${YELLOW}%s${NC} " "Möchten Sie diese Konfiguration speichern? [y/n]")" confirm
echo
if [[ "$confirm" =~ ^[Yy]$ ]]; then
# Konfiguration speichern
cat > "$config_file" << EOF
# Automatisch generierte Konfiguration
HOST=$host
PORT=$port
USER=$user
PASSWORD=$password
EOF
chmod 600 "$config_file" # Sicherstellen, dass die Datei nur für den Benutzer lesbar ist
printf "${GREEN}%s${NC}\n" "Konfiguration wurde in $config_file gespeichert."
else
printf "${YELLOW}%s${NC}\n" "Konfiguration wurde nicht gespeichert."
fiDieses Beispiel demonstriert: - Farbige Ausgaben mit
printf - Interaktive Eingaben mit read -
Standardwerte und deren Darstellung - Datenvalidierung - Formatierte
Ausgabe einer Zusammenfassung - Here-Document zur Erzeugung einer
Konfigurationsdatei
In der Shell-Programmierung spielen Exit-Codes eine zentrale Rolle für die Ablaufsteuerung und Fehlerbehandlung. Sie bilden die Grundlage für robuste Skripte, die auf unerwartete Situationen angemessen reagieren können.
Ein Exit-Code (auch Rückgabewert oder Status-Code genannt) ist ein numerischer Wert, den ein Programm oder Befehl nach seiner Ausführung an das Betriebssystem zurückgibt. Dieser Wert signalisiert, ob die Ausführung erfolgreich war oder ob Fehler aufgetreten sind.
In Unix/Linux-Systemen gilt folgende Konvention: - 0 bedeutet erfolgreiche Ausführung (kein Fehler) - Werte ungleich 0 (normalerweise 1-255) deuten auf einen Fehler hin, wobei der spezifische Wert die Art des Fehlers anzeigt
Nach der Ausführung eines Befehls kann dessen Exit-Code über die
spezielle Variable $? abgerufen werden:
ls /existierender/pfad
echo "Exit-Code: $?" # Gibt "Exit-Code: 0" aus (Erfolg)
ls /nicht/existierender/pfad
echo "Exit-Code: $?" # Gibt "Exit-Code: 2" aus (Fehler: Datei oder Verzeichnis nicht gefunden)Diese Variable enthält immer den Exit-Code des zuletzt ausgeführten Befehls oder der zuletzt ausgeführten Pipeline. Daher ist es wichtig, den Wert unmittelbar nach dem relevanten Befehl abzufragen, bevor weitere Befehle ausgeführt werden.
Obwohl jedes Programm seine eigenen Exit-Codes definieren kann, haben sich in der Unix/Linux-Welt gewisse Konventionen etabliert:
| Exit-Code | Bedeutung |
|---|---|
| 0 | Erfolgreiche Ausführung |
| 1 | Allgemeiner Fehler oder unspezifiziertes Problem |
| 2 | Fehlerhafte Verwendung der Shell-Syntax oder Befehlsoptionen |
| 126 | Befehl konnte nicht ausgeführt werden (z.B. keine Ausführungsberechtigung) |
| 127 | Befehl nicht gefunden |
| 128 | Ungültiges Exit-Argument |
| 128+n | Programm wurde durch Signal n beendet |
| 130 | Programm wurde mit CTRL-C (Signal SIGINT) beendet |
| 255 | Exit-Status außerhalb des gültigen Bereichs |
Viele bekannte Programme folgen darüber hinaus ihren eigenen
Konventionen. Zum Beispiel verwenden die Programme der GNU Coreutils
(wie grep, find usw.) oft spezifische
Exit-Codes für bestimmte Fehlersituationen.
Exit-Codes sind entscheidend für die bedingte Ausführung von Befehlen und die Ablaufkontrolle in Skripten.
&& und ||Die Operatoren && (logisches UND) und
|| (logisches ODER) erlauben die Verkettung von Befehlen
basierend auf ihrem Exit-Code:
# Der zweite Befehl wird nur ausgeführt, wenn der erste erfolgreich war (Exit-Code 0)
mkdir /tmp/testverzeichnis && echo "Verzeichnis wurde erstellt"
# Der zweite Befehl wird nur ausgeführt, wenn der erste fehlgeschlagen ist (Exit-Code ungleich 0)
grep "muster" datei.txt || echo "Muster nicht gefunden"Exit-Codes werden häufig in Bedingungen verwendet:
if grep "suchbegriff" logfile.txt; then
echo "Suchbegriff gefunden"
else
echo "Suchbegriff nicht gefunden"
fiIn diesem Beispiel wird die Meldung basierend auf dem Exit-Code von
grep ausgegeben. Wenn der Suchbegriff gefunden wird, gibt
grep den Exit-Code 0 zurück, andernfalls einen Wert
ungleich 0.
Alternativ kann der Exit-Code explizit überprüft werden:
grep "suchbegriff" logfile.txt
if [ $? -eq 0 ]; then
echo "Suchbegriff gefunden"
else
echo "Suchbegriff nicht gefunden"
fiIn eigenen Skripten können und sollten Sie aussagekräftige Exit-Codes verwenden, um den Zustand bei Beendigung zu signalisieren:
#!/bin/bash
# Exit-Code-Konstanten definieren
readonly E_SUCCESS=0
readonly E_PARAMETER_MISSING=1
readonly E_FILE_NOT_FOUND=2
readonly E_PERMISSION_DENIED=3
readonly E_INVALID_INPUT=4
# Parameter überprüfen
if [ $# -lt 1 ]; then
echo "Fehler: Dateiname als Parameter erforderlich" >&2
exit $E_PARAMETER_MISSING
fi
# Datei überprüfen
if [ ! -f "$1" ]; then
echo "Fehler: Datei '$1' nicht gefunden" >&2
exit $E_FILE_NOT_FOUND
fi
# Leserechte überprüfen
if [ ! -r "$1" ]; then
echo "Fehler: Keine Leseberechtigung für Datei '$1'" >&2
exit $E_PERMISSION_DENIED
fi
# Erfolgreiche Verarbeitung
echo "Datei '$1' erfolgreich verarbeitet"
exit $E_SUCCESS # Explizites Exit mit 0 (optional, da Skripte standardmäßig mit dem Exit-Code des letzten Befehls beendet werden)Bei der Definition eigener Exit-Codes sollten Sie: - Werte zwischen 1 und 125 verwenden (um Konflikte mit reservierten Werten zu vermeiden) - Konstanten mit aussagekräftigen Namen definieren - Die Codes und ihre Bedeutung dokumentieren - Konsistent bleiben (gleiche Fehler sollten zu gleichen Exit-Codes führen)
Bei einer Pipeline (cmd1 | cmd2 | cmd3) wird
standardmäßig der Exit-Code des letzten Befehls in der Pipeline
zurückgegeben, unabhängig davon, ob frühere Befehle fehlgeschlagen
sind.
Dieses Verhalten kann mit der Shell-Option pipefail
geändert werden:
# pipefail aktivieren: Pipeline gibt Exit-Code des ersten fehlgeschlagenen Befehls zurück
set -o pipefail
# Beispiel
grep "muster" nicht_existierende_datei.txt | sort
echo "Exit-Code: $?" # Gibt den Exit-Code von grep zurück (nicht von sort)Die effektive Behandlung von Fehlern ist entscheidend für die Entwicklung robuster Shell-Skripte. In diesem Abschnitt betrachten wir Techniken zur Fehlererkennung, -meldung und -behandlung.
Bash bietet verschiedene Optionen, die die Fehlerbehandlung verbessern können:
set -e
(errexit)Diese Option bewirkt, dass das Skript sofort beendet wird, wenn ein Befehl mit einem Exit-Code ungleich 0 endet:
#!/bin/bash
set -e
# Das Skript stoppt, sobald ein Befehl fehlschlägt
cd /nicht/existierendes/verzeichnis # Skript endet hier mit einem Fehler
echo "Diese Zeile wird nie erreicht"set -u
(nounset)Diese Option bewirkt, dass das Skript mit einem Fehler beendet wird, wenn eine nicht definierte Variable verwendet wird:
#!/bin/bash
set -u
# Das Skript stoppt, wenn eine undefinierte Variable verwendet wird
echo $undefinierte_variable # Skript endet hier mit einem Fehler
echo "Diese Zeile wird nie erreicht"set -o pipefailWie bereits erwähnt, bewirkt diese Option, dass eine Pipeline den Exit-Code des ersten fehlgeschlagenen Befehls zurückgibt, nicht nur den des letzten Befehls.
Diese Optionen können kombiniert werden, um die Robustheit zu erhöhen:
#!/bin/bash
set -euo pipefail
# Diese Einstellung wird oft als "strikte Modus" bezeichnet
# und hilft, viele häufige Fehler in Shell-Skripten zu vermeidenDie Grundlage der Fehlerbehandlung ist die Erkennung von Fehlern durch Überprüfung der Exit-Codes:
if ! command; then
echo "Der Befehl ist fehlgeschlagen" >&2
# Fehlerbehandlung
fioder
command
if [ $? -ne 0 ]; then
echo "Der Befehl ist fehlgeschlagen" >&2
# Fehlerbehandlung
fiJe nach Situation und Schwere des Fehlers können verschiedene Strategien angewandt werden:
Bei kritischen Fehlern ist es oft am besten, das Skript zu beenden:
if [ ! -f "$config_file" ]; then
echo "Kritischer Fehler: Konfigurationsdatei nicht gefunden" >&2
exit 1
fiBei transienten Fehlern kann ein erneuter Versuch sinnvoll sein:
max_attempts=3
attempt=1
while [ $attempt -le $max_attempts ]; do
if ping -c 1 server.example.com > /dev/null 2>&1; then
echo "Verbindung hergestellt"
break
else
echo "Verbindungsversuch $attempt fehlgeschlagen" >&2
attempt=$((attempt + 1))
[ $attempt -le $max_attempts ] && sleep 2
fi
done
if [ $attempt -gt $max_attempts ]; then
echo "Maximale Anzahl an Versuchen erreicht. Verbindung nicht möglich." >&2
exit 1
fiManchmal kann auf eine Alternative zurückgegriffen werden:
if ! grep -q "^$user:" /etc/passwd; then
echo "Warnung: Benutzer '$user' nicht gefunden, verwende Standardbenutzer 'nobody'" >&2
user="nobody"
fiIn einigen Fällen ist es akzeptabel, Fehler zu ignorieren:
# Versuche, temporäre Dateien zu löschen, aber ignoriere Fehler
rm -f /tmp/temp_file* 2>/dev/null || trueGute Fehlermeldungen sind: - Spezifisch und beschreiben das Problem genau - Enthalten relevante Kontextinformationen - Werden auf stderr (nicht stdout) ausgegeben - Schlagen manchmal Lösungen vor
Beispiel für eine gute Fehlerberichterstattung:
log_error() {
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
echo "[$timestamp] ERROR: $*" >&2
}
log_warning() {
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
echo "[$timestamp] WARNING: $*" >&2
}
if [ ! -f "$input_file" ]; then
log_error "Die Eingabedatei '$input_file' existiert nicht. Bitte überprüfen Sie den Dateipfad."
exit 1
fi
if [ ! -r "$input_file" ]; then
log_error "Keine Leseberechtigung für Datei '$input_file'. Bitte überprüfen Sie die Dateiberechtigungen."
exit 2
fi
if [ "$(du -k "$input_file" | cut -f1)" -eq 0 ]; then
log_warning "Die Datei '$input_file' ist leer. Die Verarbeitung wird fortgesetzt, aber es werden keine Ergebnisse erwartet."
fiDie trap-Anweisung ermöglicht das Ausführen von Code
beim Auftreten bestimmter Signale oder Ereignisse. Dies ist besonders
nützlich, um temporäre Ressourcen zu bereinigen:
#!/bin/bash
# Temporäres Verzeichnis erstellen
temp_dir=$(mktemp -d)
# Aufräumfunktion definieren
cleanup() {
echo "Aufräumen temporärer Ressourcen..." >&2
rm -rf "$temp_dir"
}
# Trap für Aufräumarbeiten bei Beendigung setzen
trap cleanup EXIT
# Trap für Signale setzen
trap 'echo "Abbruch durch Benutzer"; exit 130' INT TERM
# Hauptlogik des Skripts
echo "Arbeitsverzeichnis: $temp_dir"
echo "Einige Dateien erstellen..."
touch "$temp_dir/file1" "$temp_dir/file2"
echo "Skript läuft. Drücken Sie Ctrl+C zum Abbrechen oder warten Sie 10 Sekunden."
sleep 10
echo "Skript normal beendet."
# cleanup wird automatisch durch trap EXIT aufgerufenHier ist ein Beispiel für ein robustes Backup-Skript, das verschiedene Fehlerbehandlungstechniken demonstriert:
#!/bin/bash
# backup.sh - Ein robustes Backup-Skript mit Fehlerbehandlung
# Strikte Fehlerbehandlung aktivieren
set -euo pipefail
# Exit-Code-Konstanten
readonly E_SUCCESS=0
readonly E_ARGS=1
readonly E_SOURCE_DIR=2
readonly E_TARGET_DIR=3
readonly E_BACKUP=4
# Konfiguration
backup_date=$(date +"%Y%m%d_%H%M%S")
log_file="/var/log/backup_${backup_date}.log"
# Funktion für Fehlerberichterstattung
log() {
local level="$1"
shift
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
echo "[$timestamp] [$level] $*" | tee -a "$log_file" >&2
}
log_info() { log "INFO" "$@"; }
log_warn() { log "WARN" "$@"; }
log_error() { log "ERROR" "$@"; }
# Aufräumfunktion
cleanup() {
local exit_code=$?
log_info "Backup-Vorgang beendet mit Exit-Code $exit_code"
# Aufräumarbeiten bei Bedarf hier einfügen
exit $exit_code
}
# Trap-Handler
handle_sigint() {
log_warn "Backup-Vorgang wurde vom Benutzer abgebrochen"
exit 130
}
# Traps setzen
trap cleanup EXIT
trap handle_sigint INT TERM
# Argumente prüfen
if [ $# -ne 2 ]; then
log_error "Falsche Anzahl von Argumenten"
echo "Verwendung: $0 QUELLVERZEICHNIS ZIELVERZEICHNIS" >&2
exit $E_ARGS
fi
source_dir="$1"
target_dir="$2"
backup_file="${target_dir}/backup_${backup_date}.tar.gz"
# Quellverzeichnis prüfen
if [ ! -d "$source_dir" ]; then
log_error "Quellverzeichnis '$source_dir' existiert nicht"
exit $E_SOURCE_DIR
fi
if [ ! -r "$source_dir" ]; then
log_error "Keine Leseberechtigung für Quellverzeichnis '$source_dir'"
exit $E_SOURCE_DIR
fi
# Zielverzeichnis prüfen und ggf. erstellen
if [ ! -d "$target_dir" ]; then
log_warn "Zielverzeichnis '$target_dir' existiert nicht, versuche es zu erstellen"
if ! mkdir -p "$target_dir"; then
log_error "Konnte Zielverzeichnis '$target_dir' nicht erstellen"
exit $E_TARGET_DIR
fi
log_info "Zielverzeichnis '$target_dir' erfolgreich erstellt"
fi
if [ ! -w "$target_dir" ]; then
log_error "Keine Schreibberechtigung für Zielverzeichnis '$target_dir'"
exit $E_TARGET_DIR
fi
# Freien Speicherplatz prüfen
source_size=$(du -sk "$source_dir" | cut -f1)
target_free=$(df -k "$target_dir" | awk 'NR==2 {print $4}')
if [ $source_size -gt $target_free ]; then
log_error "Nicht genügend freier Speicherplatz im Zielverzeichnis"
log_error "Benötigt: $source_size KB, Verfügbar: $target_free KB"
exit $E_TARGET_DIR
fi
# Backup durchführen
log_info "Starte Backup von '$source_dir' nach '$backup_file'"
if ! tar -czf "$backup_file" -C "$(dirname "$source_dir")" "$(basename "$source_dir")"; then
log_error "Backup fehlgeschlagen"
# Optional: Bei Fehler erstellte unvollständige Datei entfernen
rm -f "$backup_file"
exit $E_BACKUP
fi
# Backup-Größe und Checksumme erfassen
backup_size=$(du -sh "$backup_file" | cut -f1)
backup_md5=$(md5sum "$backup_file" | cut -d ' ' -f1)
log_info "Backup erfolgreich abgeschlossen"
log_info "Backup-Datei: $backup_file"
log_info "Größe: $backup_size"
log_info "MD5-Prüfsumme: $backup_md5"
exit $E_SUCCESSDieses Skript demonstriert viele wichtige Aspekte der
Fehlerbehandlung: - Verwendung des strikten Modus
(set -euo pipefail) - Definierte Exit-Codes für
verschiedene Fehlertypen - Strukturierte Protokollierung mit
Zeitstempeln - Überprüfung von Vorbedingungen (Existenz/Berechtigungen
von Verzeichnissen) - Proaktive Prüfung auf potenzielle Probleme (freier
Speicherplatz) - Aufräumarbeiten mit trap - Signal-Handling
für Benutzerabbruch - Detaillierte Fehlermeldungen mit
Kontextinformationen
Exit-Codes und Fehlerbehandlung sind entscheidende Aspekte für die Entwicklung robuster Shell-Skripte:
set -e, set -u und
set -o pipefail verbessern die Fehlerbehandlungtrap-Anweisung ermöglicht das Aufräumen von
Ressourcen auch bei abnormaler BeendigungEine durchdachte Fehlerbehandlung unterscheidet professionelle Skripte von Ad-hoc-Lösungen. Indem Sie konsequent Exit-Codes überprüfen, angemessene Fehlerbehandlungsstrategien implementieren und informative Fehlermeldungen ausgeben, können Sie Skripte erstellen, die robust, zuverlässig und wartbar sind.
In folgenden Kapiteln werden wir auf diesen Konzepten aufbauen und fortgeschrittenere Techniken für die Fehlerbehandlung und Fehlertoleranz in komplexen Skripten kennenlernen.
In diesem Praxisbeispiel entwickeln wir ein umfassendes Bash-Skript zur Analyse und Darstellung wichtiger Systemkonfigurationen. Dieses Werkzeug kann Systemadministratoren dabei helfen, einen schnellen Überblick über den Zustand eines Systems zu gewinnen, potenzielle Probleme zu identifizieren und Dokumentation für Audit-Zwecke zu erstellen.
Bevor wir mit der Programmierung beginnen, definieren wir die Anforderungen an unser Skript:
Hier ist das vollständige Skript sysinfo.sh mit
ausführlichen Kommentaren:
#!/bin/bash
#
# sysinfo.sh - Systemkonfigurationsanalyse und -bericht
#
# Dieses Skript sammelt verschiedene Systemkonfigurationsinformationen
# und stellt sie in einem übersichtlichen Bericht dar.
#
# Verwendung:
# ./sysinfo.sh [OPTIONEN]
#
# Optionen:
# -a, --all Alle Informationen anzeigen (Standard)
# -s, --system Nur Systeminformationen anzeigen
# -n, --network Nur Netzwerkinformationen anzeigen
# -d, --disk Nur Festplatteninformationen anzeigen
# -u, --users Nur Benutzerinformationen anzeigen
# -p, --processes Nur Prozessinformationen anzeigen
# -c, --csv Ausgabe im CSV-Format (für Weiterverarbeitung)
# -o, --output FILE Ausgabe in Datei speichern
# -h, --help Diese Hilfe anzeigen
#
# Autor: Max Mustermann
# Version: 1.0
# Datum: 2025-04-10
# ===== Konfiguration und Initialisierung =====
# Strikte Fehlerbehandlung aktivieren
set -e # Exit bei Fehler
set -u # Exit bei Verwendung undefinierter Variablen
set -o pipefail # Pipeline schlägt fehl, wenn ein Befehl darin fehlschlägt
# Globale Variablen
readonly SCRIPT_NAME=$(basename "$0")
readonly VERSION="1.0"
readonly DATE=$(date +"%Y-%m-%d %H:%M:%S")
# Formatierungskonstanten für Ausgaben
readonly RESET='\033[0m'
readonly BOLD='\033[1m'
readonly RED='\033[31m'
readonly GREEN='\033[32m'
readonly YELLOW='\033[33m'
readonly BLUE='\033[34m'
readonly PURPLE='\033[35m'
readonly CYAN='\033[36m'
readonly WHITE='\033[37m'
readonly UNDERLINE='\033[4m'
# Konfigurationsoptionen (Standardwerte)
show_system=true
show_network=true
show_disk=true
show_users=true
show_processes=true
output_format="text"
output_file=""
# Exit-Codes
readonly E_SUCCESS=0
readonly E_INVALID_OPTION=1
readonly E_MISSING_COMMAND=2
readonly E_PERMISSION_DENIED=3
readonly E_OUTPUT_ERROR=4
# ===== Hilfsfunktionen =====
# Gibt eine Fehlermeldung aus und beendet das Skript
error() {
local message="$1"
local exit_code="${2:-$E_SUCCESS}"
printf "${RED}[ERROR]${RESET} %s\n" "$message" >&2
if [ "$exit_code" -ne "$E_SUCCESS" ]; then
exit "$exit_code"
fi
}
# Gibt eine Warnmeldung aus
warning() {
local message="$1"
printf "${YELLOW}[WARNING]${RESET} %s\n" "$message" >&2
}
# Gibt eine Informationsmeldung aus
info() {
local message="$1"
printf "${BLUE}[INFO]${RESET} %s\n" "$message"
}
# Prüft, ob ein Befehl verfügbar ist
command_exists() {
command -v "$1" &>/dev/null
}
# Druckt eine Abschnittsüberschrift
print_section_header() {
local title="$1"
local length=${#title}
local line=$(printf '%*s' "$length" | tr ' ' '=')
echo
printf "${BOLD}${CYAN}%s${RESET}\n" "$title"
printf "${CYAN}%s${RESET}\n" "$line"
}
# Druckt eine Zeile mit Beschreibung und Wert
print_info_line() {
local description="$1"
local value="$2"
printf "${BOLD}%-25s${RESET} : %s\n" "$description" "$value"
}
# Versucht einen Befehl auszuführen, gibt einen Fallback-Wert zurück, wenn der Befehl fehlschlägt
try_command() {
local command="$1"
local fallback="${2:-N/A}"
local result
if result=$(eval "$command" 2>/dev/null); then
echo "$result"
else
echo "$fallback"
fi
}
# Überprüft, ob das Skript mit Root-Rechten ausgeführt wird
check_root() {
if [ "$(id -u)" -ne 0 ]; then
warning "Dieses Skript wird nicht mit Root-Rechten ausgeführt."
warning "Einige Informationen könnten nicht verfügbar sein."
echo
fi
}
# Zeigt die Hilfe an
show_help() {
cat <<EOF
${BOLD}$SCRIPT_NAME${RESET} - Systemkonfigurationsanalyse und -bericht
${BOLD}VERWENDUNG:${RESET}
$SCRIPT_NAME [OPTIONEN]
${BOLD}OPTIONEN:${RESET}
-a, --all Alle Informationen anzeigen (Standard)
-s, --system Nur Systeminformationen anzeigen
-n, --network Nur Netzwerkinformationen anzeigen
-d, --disk Nur Festplatteninformationen anzeigen
-u, --users Nur Benutzerinformationen anzeigen
-p, --processes Nur Prozessinformationen anzeigen
-c, --csv Ausgabe im CSV-Format (für Weiterverarbeitung)
-o, --output FILE Ausgabe in Datei speichern
-h, --help Diese Hilfe anzeigen
${BOLD}BEISPIELE:${RESET}
$SCRIPT_NAME # Zeigt alle Informationen an
$SCRIPT_NAME -s -n # Zeigt nur System- und Netzwerkinformationen an
$SCRIPT_NAME -d -o disk.txt # Speichert Festplatteninformationen in disk.txt
$SCRIPT_NAME -a -c -o sysinfo.csv # Speichert alle Informationen im CSV-Format
${BOLD}VERSION:${RESET}
$VERSION
EOF
exit "$E_SUCCESS"
}
# ===== Informationsfunktionen =====
# Sammelt grundlegende Systeminformationen
get_system_info() {
print_section_header "Systeminformationen"
print_info_line "Hostname" "$(hostname)"
print_info_line "Kernel-Version" "$(uname -r)"
print_info_line "Betriebssystem" "$(try_command "cat /etc/os-release | grep PRETTY_NAME | cut -d= -f2 | tr -d '\"'")"
print_info_line "Architektur" "$(uname -m)"
print_info_line "Uptime" "$(try_command "uptime -p" "$(uptime)")"
# CPU-Informationen
local cpu_model=$(try_command "grep 'model name' /proc/cpuinfo | head -1 | cut -d: -f2 | sed 's/^ *//'" "N/A")
local cpu_cores=$(try_command "grep -c 'processor' /proc/cpuinfo" "N/A")
print_info_line "CPU-Modell" "$cpu_model"
print_info_line "CPU-Kerne" "$cpu_cores"
# Auslastung
local load=$(try_command "uptime | awk -F'[a-z]:' '{print \$2}'" "N/A")
print_info_line "Systemlast" "$load"
# RAM-Informationen
if command_exists free; then
local total_ram=$(free -h | awk '/^Mem/{print $2}')
local used_ram=$(free -h | awk '/^Mem/{print $3}')
local free_ram=$(free -h | awk '/^Mem/{print $4}')
print_info_line "Gesamter RAM" "$total_ram"
print_info_line "Verwendeter RAM" "$used_ram"
print_info_line "Freier RAM" "$free_ram"
else
warning "Befehl 'free' nicht gefunden. Kann RAM-Informationen nicht anzeigen."
fi
}
# Sammelt Netzwerkinformationen
get_network_info() {
print_section_header "Netzwerkinformationen"
# Hostname und lokale IP-Adresse
print_info_line "Hostname" "$(hostname)"
print_info_line "Domain" "$(hostname -d 2>/dev/null || echo "N/A")"
print_info_line "FQDN" "$(hostname -f 2>/dev/null || echo "$(hostname)")"
# Primäre IP-Adresse
local primary_ip=$(try_command "hostname -I | awk '{print \$1}'" "N/A")
print_info_line "Primäre IP-Adresse" "$primary_ip"
# Externe IP-Adresse (falls Internet verfügbar)
if command_exists curl; then
local external_ip=$(try_command "curl -s https://api.ipify.org || curl -s https://ipinfo.io/ip" "N/A")
if [ "$external_ip" != "N/A" ]; then
print_info_line "Externe IP-Adresse" "$external_ip"
else
print_info_line "Externe IP-Adresse" "Keine Internetverbindung oder curl fehlgeschlagen"
fi
else
print_info_line "Externe IP-Adresse" "curl nicht verfügbar"
fi
# Netzwerkschnittstellen
echo
echo "Netzwerkschnittstellen:"
echo "-----------------------"
if command_exists ip; then
ip -o link show | awk -F': ' '{print $2}' | while read -r interface; do
local status=$(try_command "ip link show $interface | grep 'state' | awk '{print \$9}'" "unbekannt")
local ipaddr=$(try_command "ip -o -4 addr show $interface | awk '{print \$4}'" "keine IP")
local macaddr=$(try_command "ip link show $interface | awk '/link\/ether/{print \$2}'" "keine MAC")
printf " ${BOLD}%-12s${RESET} Status: %-10s IP: %-20s MAC: %s\n" \
"$interface" "$status" "$ipaddr" "$macaddr"
done
elif command_exists ifconfig; then
ifconfig | grep -E '^[a-z]' | awk '{print $1}' | sed 's/://g' | while read -r interface; do
local status=$(try_command "ifconfig $interface | grep 'status' | awk '{print \$2}'" "unbekannt")
local ipaddr=$(try_command "ifconfig $interface | grep 'inet ' | awk '{print \$2}'" "keine IP")
local macaddr=$(try_command "ifconfig $interface | grep 'ether' | awk '{print \$2}'" "keine MAC")
printf " ${BOLD}%-12s${RESET} Status: %-10s IP: %-20s MAC: %s\n" \
"$interface" "$status" "$ipaddr" "$macaddr"
done
else
warning "Weder 'ip' noch 'ifconfig' gefunden. Kann keine Schnittstelleninformationen anzeigen."
fi
# Offene Ports anzeigen
echo
echo "Aktive Netzwerkverbindungen und offene Ports:"
echo "--------------------------------------------"
if command_exists ss; then
try_command "ss -tuln | grep LISTEN" | head -10 | sed 's/^/ /'
local connections_count=$(try_command "ss -tuln | grep LISTEN | wc -l" "0")
if [ "$connections_count" -gt 10 ]; then
echo " ... und $(($connections_count - 10)) weitere"
fi
elif command_exists netstat; then
try_command "netstat -tuln | grep LISTEN" | head -10 | sed 's/^/ /'
local connections_count=$(try_command "netstat -tuln | grep LISTEN | wc -l" "0")
if [ "$connections_count" -gt 10 ]; then
echo " ... und $(($connections_count - 10)) weitere"
fi
else
warning "Weder 'ss' noch 'netstat' gefunden. Kann keine Netzwerkverbindungen anzeigen."
fi
# DNS-Einstellungen
echo
echo "DNS-Konfiguration:"
echo "-----------------"
if [ -f /etc/resolv.conf ]; then
grep nameserver /etc/resolv.conf | sed 's/^/ /'
else
echo " Keine /etc/resolv.conf gefunden."
fi
}
# Sammelt Festplatten- und Speicherinformationen
get_disk_info() {
print_section_header "Festplatten- und Speicherinformationen"
# Dateisysteme und Belegung
echo "Dateisystembelegung:"
echo "-------------------"
if command_exists df; then
df -h | grep -v "tmpfs\|devtmpfs" | awk '{printf " %-20s %8s %8s %8s %8s %s\n", $1, $2, $3, $4, $5, $6}' | grep -v "Filesystem"
echo
else
warning "Befehl 'df' nicht gefunden. Kann Dateisysteminformationen nicht anzeigen."
fi
# Festplatten
echo "Physische Laufwerke:"
echo "-------------------"
if command_exists lsblk; then
try_command "lsblk -o NAME,SIZE,TYPE,MOUNTPOINT -p | grep -v loop | sed 's/^/ /'"
elif [ -f /proc/partitions ]; then
echo " NAME SIZE"
echo " -----------------------"
awk 'NR>2 {printf " %-16s %d MB\n", $4, $3/1024}' /proc/partitions
else
warning "Kann keine Laufwerksinformationen anzeigen."
fi
echo
# SWAP-Informationen
echo "SWAP-Nutzung:"
echo "------------"
if command_exists free; then
free -h | grep -i swap | awk '{printf " Gesamt: %s, Verwendet: %s, Frei: %s\n", $2, $3, $4}'
elif [ -f /proc/swaps ]; then
cat /proc/swaps | sed 's/^/ /'
else
warning "Kann keine SWAP-Informationen anzeigen."
fi
echo
# Größte Verzeichnisse (optional, kann langsam sein)
echo "Top 5 Verzeichnisse nach Größe (nur Root-Verzeichnisse):"
echo "------------------------------------------------------"
if command_exists du; then
try_command "du -h --max-depth=1 /var /tmp /home /opt 2>/dev/null | sort -hr | head -5 | sed 's/^/ /'"
else
warning "Befehl 'du' nicht gefunden. Kann Verzeichnisgrößen nicht anzeigen."
fi
}
# Sammelt Benutzerinformationen
get_users_info() {
print_section_header "Benutzer- und Gruppeninformationen"
# Aktuelle Benutzer im System
local users_count=$(try_command "cat /etc/passwd | wc -l" "N/A")
print_info_line "Anzahl der Benutzer" "$users_count"
# Aktive Benutzer
echo
echo "Aktuell eingeloggte Benutzer:"
echo "---------------------------"
if command_exists who; then
who | sed 's/^/ /'
else
warning "Befehl 'who' nicht gefunden."
fi
# Benutzer mit UID 0 (sollte normalerweise nur root sein)
echo
echo "Benutzer mit Root-Rechten (UID 0):"
echo "--------------------------------"
local root_users=$(try_command "grep ':0:' /etc/passwd | cut -d: -f1" "")
if [ -n "$root_users" ]; then
echo "$root_users" | sed 's/^/ /'
else
echo " Keine zusätzlichen Root-Benutzer gefunden."
fi
# Benutzer mit Shell-Zugang
echo
echo "Benutzer mit Shell-Zugang:"
echo "------------------------"
try_command "grep -v -E '/nologin$|/false$' /etc/passwd | cut -d: -f1,7 | head -10" | sed 's/^/ /'
local shell_users_count=$(try_command "grep -v -E '/nologin$|/false$' /etc/passwd | wc -l" "0")
if [ "$shell_users_count" -gt 10 ]; then
echo " ... und $(($shell_users_count - 10)) weitere"
fi
# Gruppen mit Mitgliedern
echo
echo "Wichtige Systemgruppen und ihre Mitglieder:"
echo "-----------------------------------------"
for group in sudo admin wheel adm; do
local members=$(try_command "getent group $group | cut -d: -f4" "")
if [ -n "$members" ]; then
printf " ${BOLD}%-15s${RESET}: %s\n" "$group" "$members"
fi
done
# Sudo-Konfiguration
echo
echo "Sudo-Berechtigungen (benötigt Root-Rechte für Details):"
echo "----------------------------------------------------"
if [ -f /etc/sudoers ] && [ "$(id -u)" -eq 0 ]; then
grep -v '^#\|^$' /etc/sudoers | sed 's/^/ /'
else
echo " Zugriff auf /etc/sudoers nicht möglich oder keine Root-Rechte."
echo " Allgemeine sudo-Gruppen: $(try_command "grep -v '^#\|^$' /etc/group | grep -E 'sudo|wheel|admin' | cut -d: -f1 | tr '\n' ' '")"
fi
}
# Sammelt Prozessinformationen
get_processes_info() {
print_section_header "Prozess- und Dienstinformationen"
# Laufende Prozesse
local process_count=$(try_command "ps -e | wc -l" "N/A")
print_info_line "Laufende Prozesse" "$process_count"
# Top CPU- und Speichernutzung
echo
echo "Top 5 Prozesse nach CPU-Nutzung:"
echo "------------------------------"
if command_exists ps; then
try_command "ps aux --sort=-%cpu | head -6 | awk '{printf \" %-10s %-15s %5s %5s %s\n\", \$1, \$11, \$3\"%\", \$4\"%\", \$12}'" |
sed '1s/[%][%]/%/g'
else
warning "Befehl 'ps' nicht gefunden. Kann Prozessinformationen nicht anzeigen."
fi
echo
echo "Top 5 Prozesse nach Speichernutzung:"
echo "----------------------------------"
if command_exists ps; then
try_command "ps aux --sort=-%mem | head -6 | awk '{printf \" %-10s %-15s %5s %5s %s\n\", \$1, \$11, \$3\"%\", \$4\"%\", \$12}'" |
sed '1s/[%][%]/%/g'
fi
# Systemdienste
echo
echo "Systemdienste-Status (nur wichtige Dienste):"
echo "------------------------------------------"
if command_exists systemctl; then
local services="sshd apache2 nginx httpd mysql mariadb postgresql docker cups cron"
for service in $services; do
local status=$(try_command "systemctl is-active $service 2>/dev/null" "nicht installiert")
printf " %-15s : %s\n" "$service" "$status"
done
elif command_exists service; then
local services="ssh apache2 nginx httpd mysql postgresql docker cups cron"
for service in $services; do
local status=$(try_command "service $service status >/dev/null 2>&1 && echo 'aktiv' || echo 'inaktiv'" "nicht verfügbar")
printf " %-15s : %s\n" "$service" "$status"
done
else
warning "Weder 'systemctl' noch 'service' gefunden. Kann Dienststatus nicht anzeigen."
fi
# Geplante Aufgaben
echo
echo "Geplante Cron-Aufgaben (Systemweite):"
echo "-----------------------------------"
if [ -d /etc/cron.d ] && [ "$(ls -A /etc/cron.d 2>/dev/null)" ]; then
ls -l /etc/cron.d | awk '{print $9}' | sed '/^$/d' | sed 's/^/ /'
else
echo " Keine Dateien in /etc/cron.d oder Verzeichnis nicht verfügbar."
fi
# Startup-Programme
echo
echo "Startup-Programme (systemd):"
echo "--------------------------"
if command_exists systemctl; then
try_command "systemctl list-unit-files --type=service --state=enabled | grep enabled | head -5 | awk '{print \$1}'" | sed 's/^/ /'
local enabled_count=$(try_command "systemctl list-unit-files --type=service --state=enabled | grep enabled | wc -l" "0")
if [ "$enabled_count" -gt 5 ]; then
echo " ... und $(($enabled_count - 5)) weitere"
fi
else
warning "Befehl 'systemctl' nicht gefunden. Kann Startup-Programme nicht anzeigen."
fi
}
# ===== Ausgabefunktionen =====
# Generiert den CSV-Header
generate_csv_header() {
local headers=()
if $show_system; then
headers+=("Hostname,Kernel,OS,Architektur,Uptime,CPU,Kerne,Last,RAM-Total,RAM-Used,RAM-Free")
fi
if $show_network; then
headers+=("Hostname,Domain,FQDN,IP,External-IP")
fi
if $show_disk; then
headers+=("Total-Space,Used-Space,Free-Space,Use-Percent")
fi
if $show_users; then
headers+=("Users-Count,Logged-In,Root-Users,Shell-Users")
fi
if $show_processes; then
headers+=("Process-Count,Systemd-Services,Active-Services")
fi
local IFS=','
echo "${headers[*]}"
}
# Generiert CSV-Daten
generate_csv_data() {
# Implementierung für CSV-Ausgabe würde hier erfolgen
# Dies ist ein vereinfachtes Beispiel
local data=()
if $show_system; then
local hostname=$(hostname)
local kernel=$(uname -r)
local os=$(try_command "cat /etc/os-release | grep PRETTY_NAME | cut -d= -f2 | tr -d '\"'")
local arch=$(uname -m)
local uptime=$(try_command "uptime -p" "$(uptime)")
local cpu=$(try_command "grep 'model name' /proc/cpuinfo | head -1 | cut -d: -f2 | sed 's/^ *//'")
local cores=$(try_command "grep -c 'processor' /proc/cpuinfo")
local load=$(try_command "uptime | awk -F'[a-z]:' '{print \$2}'")
local ram_total=$(try_command "free -m | awk '/^Mem/{print \$2}'")
local ram_used=$(try_command "free -m | awk '/^Mem/{print \$3}'")
local ram_free=$(try_command "free -m | awk '/^Mem/{print \$4}'")
data+=("$hostname,$kernel,$os,$arch,$uptime,$cpu,$cores,$load,$ram_total,$ram_used,$ram_free")
fi
# Weitere Datensammlung für andere Sektionen würde hier erfolgen
local IFS=','
echo "${data[*]}"
}
# ===== Hauptprogramm =====
# Kommandozeilenargumente verarbeiten
parse_arguments() {
# Wenn keine Argumente angegeben, zeige alle Informationen
if [ $# -eq 0 ]; then
return
fi
# Standardmäßig alle Sektionen deaktivieren, wenn spezifische Optionen angegeben werden
show_system=false
show_network=false
show_disk=false
show_users=false
show_processes=false
while [ $# -gt 0 ]; do
case "$1" in
-h|--help)
show_help
;;
-a|--all)
show_system=true
show_network=true
show_disk=true
show_users=true
show_processes=true
;;
-s|--system)
show_system=true
;;
-n|--network)
show_network=true
;;
-d|--disk)
show_disk=true
;;
-u|--users)
show_users=true
;;
-p|--processes)
show_processes=true
;;
-c|--csv)
output_format="csv"
;;
-o|--output)
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
output_file="$2"
shift
else
error "Option -o benötigt ein Argument." "$E_INVALID_OPTION"
fi
;;
*)
error "Unbekannte Option: $1" "$E_INVALID_OPTION"
;;
esac
shift
done
}
# Hauptfunktion
main() {
# Arguments verarbeiten
parse_arguments "$@"
# Überprüfen, ob das Skript mit Root-Rechten läuft
check_root
# Überprüfen, ob erforderliche Befehle vorhanden sind
for cmd in hostname uname grep awk; do
if ! command_exists "$cmd"; then
error "Erforderlicher Befehl nicht gefunden: $cmd" "$E_MISSING_COMMAND"
fi
done
# Ausgabeumleitung konfigurieren
if [ -n "$output_file" ]; then
# Prüfen, ob in die Datei geschrieben werden kann
if ! touch "$output_file" 2>/dev/null; then
error "Kann nicht in Datei schreiben: $output_file" "$E_OUTPUT_ERROR"
fi
# Ausgabe in Datei umleiten
exec > "$output_file"
fi
# Startmeldung ausgeben
if [ "$output_format" = "text" ]; then
printf "${BOLD}${BLUE}=== Systemkonfigurationsanalyse ===${RESET}\n"
printf "Datum: %s\n" "$DATE"
printf "Hostname: %s\n\n" "$(hostname)"
elif [ "$output_format" = "csv" ]; then
generate_csv_header
fi
# Informationen sammeln und ausgeben
if [ "$output_format" = "text" ]; then
# Die ausgewählten Informationen anzeigen
if $show_system; then
get_system_info
fi
if $show_network; then
get_network_info
fi
if $show_disk; then
get_disk_info
fi
if $show_users; then
get_users_info
fi
if $show_processes; then
get_processes_info
fi
elif [ "$output_format" = "csv" ]; then
generate_csv_data
fi
# Abschlussmeldung
if [ "$output_format" = "text" ] && [ -z "$output_file" ]; then
echo
printf "${BOLD}${GREEN}Systemanalyse abgeschlossen.${RESET}\n"
fi
exit "$E_SUCCESS"
}
# Skript mit übergebenen Argumenten starten
main "$@"Das vorgestellte Skript sysinfo.sh demonstriert die
praktische Anwendung vieler Konzepte, die im Kapitel behandelt wurden.
Im Folgenden erläutern wir die wichtigsten Designentscheidungen und
Implementierungsdetails.
Das Skript folgt einem modularen Design mit klar definierten Funktionen für unterschiedliche Aufgaben:
Dieser modulare Ansatz erleichtert die Wartung und Erweiterung des Skripts. Neue Funktionalitäten können hinzugefügt werden, ohne die bestehende Logik zu beeinträchtigen.
Das Skript implementiert mehrere Ebenen der Fehlerbehandlung:
set -euo pipefail bricht das Skript bei unerwarteten
Fehlern ab.error() und warning() sorgen für konsistente
Ausgaben.command_exists() wird überprüft, ob benötigte Befehle
vorhanden sind.try_command() fängt Fehler ab und liefert
Fallback-Werte.Dieses umfassende Fehlerbehandlungskonzept macht das Skript robust gegenüber unterschiedlichen Systemen und unerwarteten Situationen.
Das Skript berücksichtigt die Unterschiede zwischen verschiedenen Linux-Distributionen:
ip oder
ifconfig).Diese Maßnahmen stellen sicher, dass das Skript auf einer Vielzahl von Linux-Systemen funktioniert.
Das Skript bietet verschiedene Optionen für die Benutzerinteraktion:
Diese Funktionen machen das Skript vielseitig einsetzbar für verschiedene Anwendungsfälle.
Das Skript demonstriert viele im Kapitel behandelte Konzepte:
readonly für unveränderliche Werte.printf und echo.if-Anweisungen und logischen Operatoren.for- und
while-Schleifen für wiederholte Operationen.$() zur Einbettung von Befehlsausgaben.Die Ausgabe des Skripts variiert je nach System und ausgewählten Optionen. Hier ein Beispiel für die Systemsektion:
=== Systemkonfigurationsanalyse ===
Datum: 2025-04-10 15:30:45
Hostname: web-server-01
Systeminformationen
===================
Hostname : web-server-01
Kernel-Version : 5.15.0-78-generic
Betriebssystem : Ubuntu 22.04.3 LTS
Architektur : x86_64
Uptime : up 23 days, 4:12
CPU-Modell : Intel(R) Xeon(R) CPU E5-2680 v3 @ 2.50GHz
CPU-Kerne : 8
Systemlast : 0.23, 0.18, 0.15
Gesamter RAM : 16G
Verwendeter RAM : 4.8G
Freier RAM : 9.5G
Die Netzwerkinformationen könnten so aussehen:
Netzwerkinformationen
=====================
Hostname : web-server-01
Domain : example.com
FQDN : web-server-01.example.com
Primäre IP-Adresse : 192.168.1.100
Externe IP-Adresse : 203.0.113.45
Netzwerkschnittstellen:
-----------------------
eth0 Status: UP IP: 192.168.1.100/24 MAC: 00:1a:4b:cd:ef:01
eth1 Status: DOWN IP: keine IP MAC: 00:1a:4b:cd:ef:02
lo Status: UNKNOWN IP: 127.0.0.1/8 MAC: keine MAC
Aktive Netzwerkverbindungen und offene Ports:
--------------------------------------------
LISTEN 0 4096 127.0.0.1:3306 0.0.0.0:*
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
LISTEN 0 511 0.0.0.0:443 0.0.0.0:*
DNS-Konfiguration:
-----------------
nameserver 192.168.1.1
nameserver 8.8.8.8
Das Skript bietet zahlreiche Anpassungsmöglichkeiten für verschiedene Umgebungen:
Mehrere bewusste Designentscheidungen wurden getroffen:
Für Systemadministratoren ist dieses Skript in verschiedenen Szenarien nützlich: