Während wir in früheren Abschnitten die grundlegenden Ein- und Ausgabeumleitungen kennengelernt haben, ist es für fortgeschrittene Shell-Skripte unerlässlich, die volle Bandbreite an Umlenkungs- und Pipe-Mechanismen zu beherrschen. In diesem Abschnitt werden wir komplexere Anwendungsszenarien und fortgeschrittene Techniken untersuchen.
In Unix/Linux-Systemen wird jeder geöffnete File (Datei, Socket, Pipe) durch einen File Descriptor (FD) repräsentiert. Standardmäßig sind drei File Descriptors beim Start jedes Prozesses verfügbar:
0 - Standard Input (stdin)1 - Standard Output (stdout)2 - Standard Error (stderr)Die folgenden Beispiele zeigen, wie man mit diesen und zusätzlichen File Descriptors arbeiten kann:
# Umleitung von stderr zu einer Datei
command 2> error.log
# Umleitung von stdout und stderr in separate Dateien
command 1> output.log 2> error.log
# Umleitung von stderr zur gleichen Stelle wie stdout
command > output.log 2>&1
# Die moderne Schreibweise für das obige Beispiel
command &> output.logIn komplexen Skripten kann es nützlich sein, zusätzliche File Descriptors zu eröffnen:
# Öffnet FD 3 zum Schreiben in eine Datei
exec 3> logfile.txt
# Schreiben über FD 3
echo "Debugging-Information" >&3
# Schließen des FD 3
exec 3>&-Dies ist besonders praktisch für Logging in komplexen Skripten, da Sie verschiedene Ausgabeströme separat verwalten können.
Oft möchte man eine Ausgabe sowohl in eine Datei schreiben als auch auf dem Bildschirm anzeigen:
# Mittels tee
command | tee output.log
# Mit tee und Anhängen an eine Datei
command | tee -a output.log
# Auch stderr durch tee leiten
command 2>&1 | tee complete_log.txtDie Prozess-Substitution ist eine besonders mächtige Technik, die es ermöglicht, die Ausgabe eines Befehls dort zu verwenden, wo normalerweise ein Dateiname erwartet wird:
# Vergleich der Ausgabe zweier Befehle
diff <(ls -l dir1) <(ls -l dir2)
# Mehrere Quellen für einen Befehl kombinieren
grep "pattern" <(cat file1) <(echo "additional line") <(ssh remote_host cat file2)Für komplexere Eingaben bieten sich Heredocs an, die Mehrzeilentexte direkt im Skript definieren:
# Einfaches Heredoc
cat << EOF
Erste Zeile
Zweite Zeile
Eingerückte Zeile
EOF
# Heredoc mit Variablensubstitution unterdrücken
cat << 'EOT'
$HOME wird nicht ersetzt
$(hostname) wird ebenfalls nicht ausgeführt
EOT
# Heredoc mit Einrückungen für bessere Lesbarkeit
cat << 'EOF' | grep "wichtig"
Dies ist ein Text mit
mehreren Zeilen, wo wir nach
wichtigen Informationen suchen.
EOFHerestrings sind eine kompaktere Alternative für kurze Inhalte:
# Herestring
grep "muster" <<< "Dies ist ein Text mit einem Muster darin"
# Ersatz für echo | command
tr '[a-z]' '[A-Z]' <<< "in großbuchstaben umwandeln"Named Pipes (auch als FIFOs bekannt) erlauben die Kommunikation zwischen nicht verwandten Prozessen:
# Erstellen einer Named Pipe
mkfifo mypipe
# In einem Terminal: Daten in die Pipe schreiben
echo "Daten für den anderen Prozess" > mypipe
# In einem anderen Terminal: Aus der Pipe lesen
cat < mypipe
# Aufräumen
rm mypipeIm Skript könnte das so aussehen:
#!/bin/bash
# Temporäre Named Pipe erstellen
PIPE=$(mktemp -u)
mkfifo "$PIPE"
# Aufräumfunktion definieren
cleanup() {
rm -f "$PIPE"
}
trap cleanup EXIT
# Hintergrundprozess, der Daten in die Pipe schreibt
{
for i in {1..5}; do
echo "Nachricht $i"
sleep 1
done
} > "$PIPE" &
# Hauptprozess, der aus der Pipe liest und die Daten verarbeitet
while read line; do
echo "Verarbeite: $line"
# Weitere Verarbeitung hier...
done < "$PIPE"Standardmäßig gibt eine Pipe den Exit-Status des letzten Befehls zurück, was problematisch sein kann, wenn frühere Befehle in der Pipe fehlschlagen:
# Aktivieren von pipefail, um den ersten Fehler in einer Pipe zu erfassen
set -o pipefail
# Jetzt schlägt die gesamte Pipeline fehl, wenn grep fehlschlägt,
# auch wenn sort erfolgreich ist
grep "pattern" nicht_existierende_datei | sortBei komplexen Datenverarbeitungsketten kann es nützlich sein, Zwischenergebnisse zu speichern:
# Komplexe Verarbeitungspipeline mit Zwischenspeicherung
cat großedatei.log |
grep "ERROR" |
tee fehler_gesamt.log |
awk '{print $4, $5}' |
sort |
uniq -c |
tee fehler_zusammenfassung.log |
sort -nr |
head -10 > top_fehler.logDas folgende Beispiel demonstriert die Kombination mehrerer fortgeschrittener Techniken:
#!/bin/bash
set -o pipefail
# Konfiguration
LOG_FILE="/var/log/apache2/access.log"
REPORT_DIR="/var/reports/$(date +%Y-%m-%d)"
mkdir -p "$REPORT_DIR"
# File Descriptors für verschiedene Logs einrichten
exec 3> "$REPORT_DIR/errors.log"
exec 4> "$REPORT_DIR/warnings.log"
exec 5> "$REPORT_DIR/info.log"
# Logging-Funktionen
log_error() { echo "[ERROR] $(date +%H:%M:%S) - $1" >&3; }
log_warn() { echo "[WARN] $(date +%H:%M:%S) - $1" >&4; }
log_info() { echo "[INFO] $(date +%H:%M:%S) - $1" >&5; }
# Hauptverarbeitung mit Prozess-Substitution und Pipelines
log_info "Starte Log-Analyse"
# 404-Fehler extrahieren und nach Häufigkeit sortieren
grep "HTTP/1.1\" 404" "$LOG_FILE" |
awk '{print $7}' |
sort |
uniq -c |
sort -nr > "$REPORT_DIR/404_urls.txt"
# IP-Adressen mit 403-Fehlern finden
grep "HTTP/1.1\" 403" "$LOG_FILE" |
awk '{print $1}' |
sort |
uniq -c |
sort -nr > "$REPORT_DIR/forbidden_ips.txt"
# Vergleich der heutigen 404s mit gestern
YESTERDAY=$(date -d "yesterday" +%Y-%m-%d)
if [ -f "/var/reports/$YESTERDAY/404_urls.txt" ]; then
log_info "Vergleiche 404-Fehler mit gestern"
diff <(cut -d' ' -f2 "$REPORT_DIR/404_urls.txt") \
<(cut -d' ' -f2 "/var/reports/$YESTERDAY/404_urls.txt") \
> "$REPORT_DIR/new_404s.txt" || true
else
log_warn "Keine gestrigen Daten zum Vergleichen verfügbar"
fi
# Zusammenfassung mit Heredoc
cat << EOF > "$REPORT_DIR/summary.txt"
Log-Analyse für $(date +%Y-%m-%d)
================================
Top 5 404-Fehler:
$(head -5 "$REPORT_DIR/404_urls.txt")
Top 5 blockierte IPs (403):
$(head -5 "$REPORT_DIR/forbidden_ips.txt")
Gesamtanzahl der Anfragen: $(wc -l < "$LOG_FILE")
EOF
log_info "Analyse abgeschlossen, Berichte in $REPORT_DIR gespeichert"
# File Descriptors schließen
exec 3>&-
exec 4>&-
exec 5>&-Bei einigen komplexen Szenarien möchten Sie möglicherweise nur bestimmte Fehler abfangen oder umleiten:
# Nur bestimmte Fehlermeldungen in eine Datei umleiten
command 2> >(grep "wichtiger Fehler" > gefilterte_fehler.log)
# Fehler in eine Datei schreiben und gleichzeitig auf stderr ausgeben
command 2> >(tee -a error.log >&2)Here-Dokumente (Heredocs) und Here-Strings gehören zu den mächtigsten Werkzeugen für die Eingabesteuerung in Shell-Skripten. Sie ermöglichen die Integration von Mehrzeilentexten direkt in Ihre Skripte und bieten flexible Optionen zur Verarbeitung dieser Texte. In diesem Abschnitt werden wir diese Konzepte im Detail untersuchen und ihre praktischen Anwendungsfälle demonstrieren.
Ein Here-Dokument erlaubt es, einen Textblock direkt im Skript zu definieren und als Standardeingabe an einen Befehl zu übergeben. Die grundlegende Syntax ist:
command << DELIMITER
text content
more text content
DELIMITERHere-Dokumente werden mit dem Umleitungsoperator
<< eingeleitet, gefolgt von einem selbst gewählten
Begrenzungszeichen (Delimiter):
cat << EOF
Dies ist ein mehrzeiliger Text.
Er wird als Eingabe für den cat-Befehl verwendet.
EOFDer Textblock beginnt in der nächsten Zeile nach dem Delimiter und endet, wenn der Delimiter erneut auf einer eigenen Zeile erscheint.
Standardmäßig findet in Heredocs eine Variablensubstitution statt:
name="Alice"
cat << EOF
Hallo, $name!
Heute ist $(date +%d.%m.%Y).
EOFAusgabe:
Hallo, Alice!
Heute ist 10.04.2025.
Wenn Sie die Variablensubstitution unterdrücken möchten, setzen Sie den Delimiter in einfache Anführungszeichen:
cat << 'EOF'
In diesem Text werden $variablen und $(befehle)
nicht interpretiert oder ersetzt.
EOFBei komplexeren Skripten ist es oft wünschenswert, den Heredoc-Inhalt
einzurücken, um die Lesbarkeit zu verbessern. Mit dem
<<- Operator können Sie Tabulatoren (nicht
Leerzeichen!) am Anfang jeder Zeile im Heredoc ignorieren:
if true; then
cat <<- EOF
Dieser Text ist im Skript eingerückt,
aber die Ausgabe enthält diese Einrückungen nicht.
Der Abschluss-Delimiter kann ebenfalls eingerückt sein.
EOF
fiHere-Dokumente lassen sich nahtlos mit anderen Umleitungen und Pipes kombinieren:
# Heredoc mit Ausgabeumleitung
cat << EOF > konfiguration.txt
user=admin
password=sicher123
host=localhost
EOF
# Heredoc mit Pipe
cat << EOF | grep "wichtig"
Dies ist eine Zeile.
Diese Zeile ist wichtig.
Dies ist noch eine Zeile.
EOFcat << EOF > /etc/nginx/sites-available/mysite.conf
server {
listen 80;
server_name ${DOMAIN};
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
}
}
EOFmysql -u user -p database << SQL
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO users (username, email) VALUES ('admin', 'admin@example.com');
SQLssh user@remote_server bash << 'ENDSSH'
cd /var/log
find . -name "*.log" -type f -mtime +30 | xargs rm -f
echo "Alte Logs wurden bereinigt"
ENDSSH: << 'KOMMENTAR'
Dies ist ein mehrzeiliger Kommentar.
Der Befehl ':' tut nichts, und die Ausgabe
des Heredocs wird an ihn übergeben und ignoriert.
KOMMENTARHere-Strings sind eine kompaktere Alternative zu Heredocs, wenn Sie
nur einen einzelnen String als Eingabe übergeben möchten. Die Syntax
verwendet den Operator <<<:
command <<< "string"# Zählt die Wörter im übergebenen String
wc -w <<< "Dies sind fünf Wörter"Ausgabe:
5
Wie bei Heredocs findet in Here-Strings Variablensubstitution statt:
name="Bob"
grep "Bob" <<< "Hallo, mein Name ist $name"# Umwandlung in Großbuchstaben
tr 'a-z' 'A-Z' <<< "konvertiere diesen text in großbuchstaben"grep "^ERROR" <<< "INFO: Alles normal\nERROR: Etwas ist schiefgelaufen"# Anstatt:
echo "user:password" | cut -d':' -f1
# Kann man schreiben:
cut -d':' -f1 <<< "user:password"# Auch mehrzeilige Strings funktionieren
grep "Zeile 2" <<< "Zeile 1
Zeile 2
Zeile 3"Here-Dokumente können innerhalb von Funktionen verwendet werden, um wiederkehrende Textvorlagen zu generieren:
generate_html() {
local title="$1"
local content="$2"
cat << EOF
<!DOCTYPE html>
<html>
<head>
<title>$title</title>
</head>
<body>
<h1>$title</h1>
<div class="content">
$content
</div>
</body>
</html>
EOF
}
# Verwendung
generate_html "Meine Seite" "Willkommen auf meiner Webseite!" > index.htmlBei spezialisierten Anwendungen kann es nützlich sein, dynamische Delimiter zu verwenden, um Konflikte zu vermeiden:
DELIMITER="ENDE_$(date +%s)"
cat << $DELIMITER
Dieser Text könnte problematische Inhalte wie EOF enthalten,
aber da wir einen zufälligen Delimiter verwenden, gibt es keine Konflikte.
$DELIMITERManchmal möchten Sie nur bestimmte Variablen im Heredoc ersetzen, aber andere unverändert lassen:
username="admin"
# $username wird ersetzt, $HOME bleibt unverändert
cat << EOF | sed "s/\$HOME/\/custom\/path/"
Benutzer: $username
Pfad: $HOME
EOFHere-Dokumente eignen sich hervorragend, um dynamisch neue Skripte zu erzeugen:
#!/bin/bash
# Parameter für das neue Skript
backup_dir="/var/backups"
log_file="/var/log/backup.log"
# Erzeugung eines Backup-Skripts
cat << 'EOF' > /usr/local/bin/daily-backup.sh
#!/bin/bash
# Automatisch generiertes Backup-Skript
EOF
# Jetzt fügen wir dynamische Inhalte hinzu
cat << EOF >> /usr/local/bin/daily-backup.sh
BACKUP_DIR="$backup_dir"
LOG_FILE="$log_file"
echo "Backup gestartet am \$(date)" >> \$LOG_FILE
rsync -av /home/ \$BACKUP_DIR/\$(date +%Y%m%d)/ >> \$LOG_FILE 2>&1
echo "Backup abgeschlossen am \$(date)" >> \$LOG_FILE
EOF
# Ausführbar machen
chmod +x /usr/local/bin/daily-backup.shWann sollten Sie Heredocs oder Here-Strings verwenden? Hier einige Richtlinien:
| Szenario | Empfohlene Technik |
|---|---|
| Mehrzeiliger Text | Here-Dokument |
| Einzeiliger Text | Here-String |
| Komplexe Textformatierung | Here-Dokument |
| Dynamische Skripterzeugung | Here-Dokument |
Ersetzen von echo "text" \| command |
Here-String |
| Große Konfigurationsdateien | Here-Dokument mit Ausgabeumleitung |
Prozesssubstitution ist eine fortgeschrittene Technik in der Shell-Programmierung, die es ermöglicht, die Ausgabe von Befehlen direkt als Datei-ähnliche Objekte zu behandeln. Diese leistungsstarke Funktion wird häufig übersehen, kann aber die Lesbarkeit und Effizienz von Shell-Skripten erheblich verbessern. In diesem Abschnitt werden wir die Prozesssubstitution detailliert untersuchen und ihre praktischen Anwendungsfälle demonstrieren.
Prozesssubstitution stellt die Ausgabe eines Befehls oder einer Pipeline als temporäre Datei dar, auf die andere Befehle zugreifen können. Statt komplexe temporäre Dateien zu erstellen, können Sie diesen Mechanismus nutzen, um Daten direkt zwischen Prozessen zu übertragen.
Die Syntax für Prozesssubstitution verwendet die Operatoren
<(command) für Lesezugriff und
>(command) für Schreibzugriff:
# Lesezugriff: <(command)
command1 <(command2)
# Schreibzugriff: >(command)
command1 >(command2)Wenn die Shell diese Konstrukte sieht, führt sie den Befehl in den
Klammern aus und ersetzt den Ausdruck durch einen temporären Dateipfad
(meistens in Form von /dev/fd/N), der auf eine Pipe zum
Prozess verweist.
<(command))Die häufigere Form der Prozesssubstitution ist die lesende Form
<(command), bei der die Ausgabe eines Befehls als Datei
für einen anderen Befehl verfügbar gemacht wird.
Ein klassisches Beispiel ist der Vergleich der Ausgaben zweier Befehle:
# Vergleich der Verzeichnisinhalte von zwei Ordnern
diff <(ls -l /etc/apache2) <(ls -l /etc/apache2.old)
# Vergleich der aktiven Prozesse zu zwei verschiedenen Zeitpunkten
diff <(ps aux) <(sleep 5; ps aux)Ohne Prozesssubstitution müssten temporäre Dateien erstellt werden:
# Ältere Methode mit temporären Dateien
ls -l /etc/apache2 > /tmp/dir1
ls -l /etc/apache2.old > /tmp/dir2
diff /tmp/dir1 /tmp/dir2
rm /tmp/dir1 /tmp/dir2Befehle, die nur eine einzelne Eingabedatei unterstützen, können mit Prozesssubstitution trotzdem mit mehreren Quellen arbeiten:
# Suche nach Mustern in der Ausgabe mehrerer Befehle
grep "error" <(cat /var/log/syslog) <(journalctl -xe) <(cat /var/log/apache2/error.log)
# Verarbeite die kombinierte Ausgabe mehrerer Befehle
sort -u <(cat file1.txt) <(cat file2.txt) <(echo "Zusätzliche Zeile")Prozesssubstitution eignet sich hervorragend, um mit Remote-Daten zu arbeiten, ohne temporäre Dateien zu erstellen:
# Vergleich einer lokalen mit einer Remote-Datei
diff <(cat local_file.txt) <(ssh remote_server cat remote_file.txt)
# Suche in Remote-Logs
grep "kritischer Fehler" <(ssh webserver cat /var/log/nginx/error.log)>(command))Die schreibende Form der Prozesssubstitution
>(command) ermöglicht es, Daten in einen verarbeitenden
Befehl zu leiten, während der Hauptbefehl weiterläuft.
Besonders nützlich ist dies für parallele Verarbeitung und Protokollierung:
# Ausgabe sowohl an die Konsole als auch in eine Datei schreiben
echo "Wichtige Nachricht" | tee >(logger)
# Daten erfassen und gleichzeitig verschiedene Analysen durchführen
cat huge_log.txt > >(grep "ERROR" > errors.log) > >(grep "WARNING" > warnings.log)Mit der schreibenden Prozesssubstitution können Sie einen Datenstrom auf mehrere Verarbeitungspfade aufteilen:
# Verteile die Ausgabe auf mehrere Verarbeitungspipelines
cat access.log | tee >(awk '/404/ {print $1}' | sort -u > not_found_ips.txt) \
>(awk '/503/ {print $1}' | sort -u > service_unavailable_ips.txt) \
>(grep -i "bot" > bot_requests.log) \
> /dev/nullIn fortgeschrittenen Szenarien können beide Formen der Prozesssubstitution kombiniert werden:
# Komplexes Beispiel: Daten aus einer Quelle lesen, aufteilen und verarbeiten
cat <(zcat log_archive.gz) |
tee >(grep "ERROR" > errors.log) |
awk '{count[$1]++} END {for (ip in count) print ip, count[ip]}' |
sort -k2 -nr > ip_frequency.txt#!/bin/bash
# Zwei CSV-Dateien mit gemeinsamer Schlüsselspalte zusammenführen
# und gleichzeitig transformieren
join -t, -1 1 -2 1 \
<(sort -t, -k1 users.csv) \
<(sort -t, -k1 orders.csv) |
awk -F, '{print $1","$2","$4","$5}' > combined_report.csv#!/bin/bash
# Systemstatus erfassen und in verschiedene Berichte aufteilen
current_date=$(date +"%Y-%m-%d_%H-%M-%S")
# Systemdaten sammeln und in verschiedene Berichte aufteilen
{
echo "=== Systemstatus vom $current_date ==="
echo
echo "=== CPU-Auslastung ==="
top -bn1 | head -20
echo
echo "=== Speichernutzung ==="
free -h
echo
echo "=== Festplattennutzung ==="
df -h
echo
echo "=== Netzwerkverbindungen ==="
netstat -tuln
} | tee >(grep -A10 "CPU" > $current_date-cpu.log) \
>(grep -A8 "Speicher" > $current_date-memory.log) \
>(grep -A8 "Festplatten" > $current_date-disk.log) \
>(grep -A15 "Netzwerk" > $current_date-network.log) \
> $current_date-full.log#!/bin/bash
# Daten aus einer alten Datenbank in eine neue migrieren,
# mit Backup und Transformation
mysqldump -u olduser -poldpass olddb |
tee >(gzip > olddb_backup.sql.gz) |
sed 's/`old_prefix_/`new_prefix_/g' |
mysql -u newuser -pnewpass newdb#!/bin/bash
# Kontinuierliche Überwachung eines Logs mit verschiedenen Filtern
tail -f /var/log/application.log |
tee >(grep "ERROR" | ts "%Y-%m-%d %H:%M:%S" >> errors.log) \
>(grep "WARN" | ts "%Y-%m-%d %H:%M:%S" >> warnings.log) \
>(grep -i "security" | ts "%Y-%m-%d %H:%M:%S" >> security.log) \
| grep --color=always -E "ERROR|WARN|$"Unter der Haube verwendet die Shell für Prozesssubstitution benannte
Pipes oder spezielle /dev/fd/ Dateien:
/dev/fd/63, der auf die Pipeline verweist.Sie können dies selbst beobachten:
echo <(echo "test") # Zeigt etwas wie /dev/fd/63 anBeachten Sie die folgenden Einschränkungen:
Shell-Unterstützung: Prozesssubstitution wird von Bash, Zsh und Ksh unterstützt, aber nicht von POSIX-konformen Shells wie Dash.
Rückgabewerte: Es kann schwierig sein, die Rückgabewerte von Befehlen innerhalb einer Prozesssubstitution zu erfassen.
Parallele Ausführung: Befehle in einer Prozesssubstitution laufen parallel zum Hauptbefehl, was zu unerwarteten Timing-Problemen führen kann.
Für bessere POSIX-Kompatibilität können Sie explizit Bash verwenden:
#!/usr/bin/env bashProzesssubstitution ist im Allgemeinen effizienter als temporäre Dateien zu verwenden, da:
Bei sehr großen Datenmengen sollten Sie jedoch die Auswirkungen auf den Speicher im Auge behalten.
Prozesssubstitution ist besonders nützlich, wenn:
In folgenden Situationen sind möglicherweise andere Techniken besser geeignet:
|)
ausreichen.In komplexen Shell-Skripten reicht die grundlegende Umleitung von Standard-Ein- und Ausgabeströmen oft nicht aus. Für anspruchsvolle Automatisierungsaufgaben und Datenverarbeitung ist es essentiell, mehrere Ein- und Ausgabeströme gleichzeitig zu kontrollieren und zu koordinieren. Dieser Abschnitt behandelt fortgeschrittene Techniken zum Arbeiten mit mehreren Datenströmen.
In Unix/Linux-Systemen wird jede geöffnete Datei, jeder Socket und jede Pipe durch einen numerischen File Descriptor (FD) repräsentiert. Standardmäßig sind diese drei File Descriptors für jeden Prozess definiert:
0 (stdin): Standard-Eingabe1 (stdout): Standard-Ausgabe2 (stderr): Standard-FehlerausgabeAber das System unterstützt weit mehr als nur diese drei. In den meisten Shell-Implementierungen können Sie mit bis zu 9 FDs arbeiten, in fortgeschrittenen Szenarien manchmal sogar mehr.
Mit dem exec-Befehl können Sie zusätzliche File
Descriptors öffnen und mit ihnen arbeiten:
# FD 3 zum Schreiben in eine Datei öffnen
exec 3> output.log
# Etwas über FD 3 schreiben
echo "Diese Nachricht geht in die Log-Datei" >&3
# FD 3 schließen
exec 3>&-Dieser Ansatz ist besonders nützlich bei komplexen Skripten, die verschiedene Arten von Ausgaben an unterschiedliche Ziele senden müssen.
Ein häufiger Anwendungsfall für mehrere Ausgabeströme ist die Implementierung von Log-Kanälen mit verschiedenen Detailstufen:
#!/bin/bash
# Log-Datei-Handles öffnen
exec 3> /var/log/script_error.log # Nur Fehler
exec 4> /var/log/script_warn.log # Warnungen und Fehler
exec 5> /var/log/script_info.log # Info, Warnungen und Fehler
exec 6> /var/log/script_debug.log # Alle Meldungen inkl. Debug
# Log-Funktionen
log_error() {
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
echo "[$timestamp] ERROR: $1" >&3
echo "[$timestamp] ERROR: $1" >&4
echo "[$timestamp] ERROR: $1" >&5
echo "[$timestamp] ERROR: $1" >&6
echo "ERROR: $1" >&2 # Auch auf stderr ausgeben
}
log_warn() {
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
echo "[$timestamp] WARNING: $1" >&4
echo "[$timestamp] WARNING: $1" >&5
echo "[$timestamp] WARNING: $1" >&6
}
log_info() {
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
echo "[$timestamp] INFO: $1" >&5
echo "[$timestamp] INFO: $1" >&6
}
log_debug() {
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
echo "[$timestamp] DEBUG: $1" >&6
}
# Beispiel-Verwendung
log_debug "Initialisiere Skript"
log_info "Skript gestartet"
log_warn "Konfigurationsdatei nicht gefunden, verwende Standardwerte"
log_error "Kritischer Fehler: Datenbankverbindung fehlgeschlagen"
# Aufräumen am Ende
exec 3>&-
exec 4>&-
exec 5>&-
exec 6>&-Sie können einen File Descriptor duplizieren, um seine Ausgabe zu kopieren oder zu sichern:
# Sichere stdout in FD 3
exec 3>&1
# Umleitung von stdout in eine Datei
exec 1> output.log
# Führe Befehle aus, deren Ausgabe in die Datei geht
echo "Diese Zeile geht in die Datei"
# Stelle stdout von FD 3 wieder her
exec 1>&3
# Schließe den temporären FD
exec 3>&-
echo "Diese Zeile erscheint wieder auf dem Terminal"Diese Technik ist besonders nützlich, wenn Sie temporär die Ausgabe umleiten und später wieder zum ursprünglichen Zustand zurückkehren möchten.
Sie können einen Block von Befehlen in geschweifte Klammern einschließen und ihre gesamte Ausgabe auf einmal umleiten:
# Ausgabe eines Befehls-Blocks in eine Datei umleiten
{
echo "Systemdiagnose"
echo "==============="
date
uname -a
df -h
free -m
ps aux | grep httpd
} > system_report.txt
# Ausgabe- und Fehlerströme separat erfassen
{
echo "Start der Datenbanksicherung"
mysqldump --all-databases
echo "Sicherung abgeschlossen"
} > backup.sql 2> backup_errors.logDies ist ein sauberer Weg, um zusammenhängende Befehlsgruppen zu verarbeiten.
Ein fortgeschrittenes Muster ist das vorübergehende Umschalten zwischen verschiedenen Ausgabezielen:
#!/bin/bash
# Standard-Ausgabeströme sichern
exec 3>&1 # Sichere stdout
exec 4>&2 # Sichere stderr
# Standardmäßig ausführliche Meldungen ausgeben
VERBOSE=true
# Funktion zum Ausführen von Befehlen mit kontrollierter Ausgabe
run_command() {
local cmd="$1"
local desc="$2"
echo "Führe aus: $desc"
if [ "$VERBOSE" = true ]; then
# Verbose Modus: Alle Ausgaben anzeigen
eval "$cmd"
else
# Stiller Modus: Ausgaben unterdrücken, nur Fehler anzeigen
exec 1>/dev/null
eval "$cmd"
exec 1>&3 # stdout wiederherstellen
fi
}
# Beispiel-Verwendung
run_command "ls -la /etc" "Auflisten von Dateien in /etc"
# Stiller Modus
VERBOSE=false
run_command "find /var -type f -name '*.log'" "Suche nach Log-Dateien"
# Ausgabeströme wiederherstellen
exec 1>&3
exec 2>&4
exec 3>&-
exec 4>&-So wie wir mehrere Ausgabeströme handhaben können, können wir auch mit mehreren Eingabequellen arbeiten:
#!/bin/bash
# Öffne verschiedene Dateien als Eingabeströme
exec 3< datei1.txt
exec 4< datei2.txt
# Lese abwechselnd aus beiden Dateien
while IFS= read -r line1 <&3 && IFS= read -r line2 <&4; do
echo "Datei 1: $line1"
echo "Datei 2: $line2"
done
# Schließe die File Descriptors
exec 3<&-
exec 4<&-Diese Technik ist nützlich, wenn Sie mehrere Datenströme parallel verarbeiten müssen.
In fortgeschrittenen Szenarien können Sie sowohl Eingabe- als auch Ausgabeströme für die bidirektionale Kommunikation mit einem Unterprozess verwenden:
#!/bin/bash
# Öffne eine bidirektionale Pipe zu einem Programm
coproc bc # Starte bc als Koprozess
# Sende Berechnungen an bc und lese Ergebnisse
echo "2 + 2" >&"${COPROC[1]}"
read -r result <&"${COPROC[0]}"
echo "Ergebnis: $result"
echo "sqrt(16)" >&"${COPROC[1]}"
read -r result <&"${COPROC[0]}"
echo "Ergebnis: $result"
# Beende den Koprozess
echo "quit" >&"${COPROC[1]}"Diese Technik ist besonders für die Interaktion mit interaktiven Programmen wie Interpretern oder Datenbanken nützlich.
Named Pipes (auch als FIFOs bekannt) bieten eine Möglichkeit, mehrere Prozesse in komplexen Datenflussszenarien zu verbinden:
#!/bin/bash
# Erstelle Named Pipes
PIPE1=$(mktemp -u)
PIPE2=$(mktemp -u)
mkfifo "$PIPE1"
mkfifo "$PIPE2"
# Aufräumen bei Beendigung
trap 'rm -f "$PIPE1" "$PIPE2"' EXIT
# Prozess 1: Generiere Daten und schreibe in PIPE1
{
for i in {1..10}; do
echo "Datensatz $i"
sleep 0.5
done
} > "$PIPE1" &
# Prozess 2: Lese aus PIPE1, verarbeite und schreibe in PIPE2
{
while IFS= read -r line; do
echo "Verarbeitet: ${line^^}" # Umwandlung in Großbuchstaben
done < "$PIPE1"
} > "$PIPE2" &
# Prozess 3: Lese das Endergebnis aus PIPE2
while IFS= read -r line; do
echo "Endgültiges Ergebnis: $line"
done < "$PIPE2"Named Pipes ermöglichen komplexe Datenflussarchitekturen und asynchrone Kommunikation zwischen Prozessen.
Das folgende Beispiel demonstriert, wie man mehrere Ein- und Ausgabeströme in einem realen Szenario verwenden kann:
#!/bin/bash
# Dieses Skript verarbeitet eine Logdatei, extrahiert verschiedene Arten von Events
# und speichert sie in separaten Dateien, während es Echtzeit-Statistiken anzeigt.
# Definiere Log-Dateien
ERROR_LOG="errors.log"
WARN_LOG="warnings.log"
INFO_LOG="info.log"
ACCESS_LOG="access.log"
STATS_FILE="statistics.txt"
# Lösche bestehende Log-Dateien
> "$ERROR_LOG"
> "$WARN_LOG"
> "$INFO_LOG"
> "$ACCESS_LOG"
> "$STATS_FILE"
# Öffne File Descriptors für die Log-Dateien
exec 3> "$ERROR_LOG"
exec 4> "$WARN_LOG"
exec 5> "$INFO_LOG"
exec 6> "$ACCESS_LOG"
exec 7> "$STATS_FILE"
# Zähler initialisieren
errors=0
warnings=0
infos=0
accesses=0
# Sichere stdout für spätere Wiederherstellung
exec 8>&1
# Funktion zum Aktualisieren der Statistiken
update_stats() {
# Ausgabe auf Standard-stdout umleiten
exec 1>&8
# Aktuelle Statistiken anzeigen
echo -ne "\rVerarbeitet: Fehler: $errors | Warnungen: $warnings | Infos: $infos | Zugriffe: $accesses"
# Auch in Statistik-Datei schreiben
echo "$(date +%H:%M:%S) - Fehler: $errors | Warnungen: $warnings | Infos: $infos | Zugriffe: $accesses" >&7
}
# Hauptverarbeitungsschleife
while IFS= read -r line; do
# Kategorisiere jede Zeile basierend auf ihrem Inhalt
if [[ "$line" == *"ERROR"* ]]; then
echo "$line" >&3
((errors++))
elif [[ "$line" == *"WARN"* ]]; then
echo "$line" >&4
((warnings++))
elif [[ "$line" == *"INFO"* ]]; then
echo "$line" >&5
((infos++))
elif [[ "$line" == *"GET"* || "$line" == *"POST"* ]]; then
echo "$line" >&6
((accesses++))
fi
# Statistiken alle 10 Einträge aktualisieren
if (( (errors + warnings + infos + accesses) % 10 == 0 )); then
update_stats
fi
done < "combined.log" # Die zu verarbeitende Log-Datei
# Abschließende Statistiken
update_stats
echo # Neue Zeile nach dem Update
# Schließe alle File Descriptors
exec 3>&-
exec 4>&-
exec 5>&-
exec 6>&-
exec 7>&-
exec 8>&-
echo "Verarbeitung abgeschlossen. Log-Dateien wurden erstellt."
echo "Zusammenfassung:"
echo " - Fehler: $errors (gespeichert in $ERROR_LOG)"
echo " - Warnungen: $warnings (gespeichert in $WARN_LOG)"
echo " - Infos: $infos (gespeichert in $INFO_LOG)"
echo " - Zugriffe: $accesses (gespeichert in $ACCESS_LOG)"
echo " - Fortlaufende Statistiken: $STATS_FILE"Beim Arbeiten mit mehreren Ein- und Ausgabeströmen sollten Sie folgende Best Practices beachten:
Immer aufräumen: Schließen Sie alle geöffneten File Descriptors vor Beendigung des Skripts, um Ressourcenlecks zu vermeiden.
Nutzung von trap: Verwenden Sie
trap-Befehle, um sicherzustellen, dass Aufräumarbeiten auch bei
unerwartetem Skriptabbruch durchgeführt werden.
Kommentieren Sie Ihre FDs: Bei komplexen Skripten mit vielen FDs sollten Sie deren Verwendungszweck klar kommentieren.
Verwenden Sie sprechende Variablen: Anstatt direkt mit Zahlen zu arbeiten, können Sie Variablen für bessere Lesbarkeit verwenden:
ERROR_OUT=3
WARN_OUT=4
exec $ERROR_OUT> error.log
echo "Fehlermeldung" >&$ERROR_OUTFehlerbehandlung: Überprüfen Sie, ob das Öffnen der File Descriptors erfolgreich war, besonders bei kritischen Operationen.
Vermeiden Sie zu komplizierte Konstrukte: Wenn ein Design zu komplex wird, erwägen Sie, es in separate Skripte aufzuteilen oder eine stärkere Programmiersprache zu verwenden.
Interaktive Shell-Skripte, die mit Benutzereingaben arbeiten, bieten eine leistungsstarke Möglichkeit, anpassbare und benutzerfreundliche Automatisierungslösungen zu erstellen. In diesem Abschnitt werden wir verschiedene Techniken zur Erfassung, Validierung und Verarbeitung von Benutzereingaben untersuchen, sowie Methoden zur Erstellung ansprechender interaktiver Benutzeroberflächen in der Shell.
readDer read-Befehl ist das Hauptwerkzeug zur Erfassung von
Benutzereingaben in Shell-Skripten:
#!/bin/bash
# Einfache Eingabeaufforderung
echo "Wie lautet dein Name?"
read name
echo "Hallo, $name!"
# Mehrere Variablen in einer Zeile
echo "Gib deinen Vor- und Nachnamen ein:"
read vorname nachname
echo "Hallo, $vorname $nachname!"read-BefehlsDer read-Befehl verfügt über zahlreiche Optionen, die
seine Funktionalität erheblich erweitern:
# Timeout für Eingabe (5 Sekunden)
read -t 5 -p "Schnell entscheiden (5 Sek.): " antwort
echo "Deine Antwort: ${antwort:-Keine Eingabe}"
# Eingabeaufforderung im selben Befehl
read -p "Serveradresse: " server
# Passwort ohne Anzeige eingeben
read -s -p "Passwort: " passwort
echo # Neue Zeile nach verdeckter Eingabe
# Begrenzte Anzahl von Zeichen einlesen
read -n 1 -p "Drücke eine Taste, um fortzufahren... " taste
# Mit Vorgabewert
read -p "Sprache [de]: " sprache
sprache=${sprache:-de}Bei interaktiven Skripten ist es wichtig, Benutzereingaben zu validieren und angemessen auf ungültige Eingaben zu reagieren:
#!/bin/bash
# Funktion zur Validierung numerischer Eingaben
validate_number() {
local input=$1
local min=$2
local max=$3
# Prüfe, ob die Eingabe eine gültige Zahl ist
if ! [[ "$input" =~ ^[0-9]+$ ]]; then
echo "Fehler: Bitte gib eine gültige Zahl ein."
return 1
fi
# Prüfe Bereich, falls angegeben
if [ -n "$min" ] && [ "$input" -lt "$min" ]; then
echo "Fehler: Die Zahl muss mindestens $min sein."
return 1
fi
if [ -n "$max" ] && [ "$input" -gt "$max" ]; then
echo "Fehler: Die Zahl darf höchstens $max sein."
return 1
fi
return 0
}
# Wiederholte Eingabeaufforderung bis zur gültigen Eingabe
get_valid_number() {
local prompt=$1
local min=$2
local max=$3
local value
while true; do
read -p "$prompt: " value
if validate_number "$value" "$min" "$max"; then
echo "$value"
return 0
fi
done
}
# Beispielverwendung
echo "Server-Konfiguration"
port=$(get_valid_number "Gib den Port ein (1024-65535)" 1024 65535)
echo "Server wird auf Port $port konfiguriert."Für komplexere Validierungen sind reguläre Ausdrücke (Regex) unverzichtbar:
#!/bin/bash
# Funktion zur Validierung einer E-Mail-Adresse
validate_email() {
local email=$1
local email_regex="^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
if [[ "$email" =~ $email_regex ]]; then
return 0
else
echo "Fehler: '$email' ist keine gültige E-Mail-Adresse."
return 1
fi
}
# Funktion zur Validierung einer IP-Adresse
validate_ip() {
local ip=$1
local ip_regex="^([0-9]{1,3}\.){3}[0-9]{1,3}$"
if ! [[ "$ip" =~ $ip_regex ]]; then
echo "Fehler: Falsches IP-Format."
return 1
fi
# Überprüfe, ob jede Oktett zwischen 0 und 255 liegt
IFS='.' read -r -a octets <<< "$ip"
for octet in "${octets[@]}"; do
if [[ "$octet" -gt 255 ]]; then
echo "Fehler: IP-Oktett > 255."
return 1
fi
done
return 0
}
# Eingabe mit Validierung
while true; do
read -p "E-Mail-Adresse: " email
if validate_email "$email"; then
break
fi
done
echo "Gültige E-Mail: $email"Menüs bieten eine benutzerfreundliche Möglichkeit, in Shell-Skripten Optionen auszuwählen:
#!/bin/bash
# Funktion zur Anzeige eines einfachen Menüs
show_menu() {
clear
echo "==== System Administration Menü ===="
echo "1) Systemstatus anzeigen"
echo "2) Festplattenbelegung prüfen"
echo "3) Benutzer verwalten"
echo "4) Dienste verwalten"
echo "0) Beenden"
echo "================================="
}
# Hauptmenüschleife
while true; do
show_menu
read -p "Wähle eine Option: " option
case "$option" in
1)
echo "Systemstatus:"
uptime
free -h
read -n 1 -p "Drücke eine Taste, um fortzufahren..."
;;
2)
echo "Festplattenbelegung:"
df -h
read -n 1 -p "Drücke eine Taste, um fortzufahren..."
;;
3)
echo "Benutzerverwaltung noch nicht implementiert."
read -n 1 -p "Drücke eine Taste, um fortzufahren..."
;;
4)
echo "Dienstverwaltung noch nicht implementiert."
read -n 1 -p "Drücke eine Taste, um fortzufahren..."
;;
0)
echo "Programm wird beendet."
exit 0
;;
*)
echo "Ungültige Option!"
read -n 1 -p "Drücke eine Taste, um fortzufahren..."
;;
esac
doneFür komplexere Anwendungen können Sie verschachtelte oder hierarchische Menüs implementieren:
#!/bin/bash
# Hauptmenü-Funktion
main_menu() {
clear
echo "===== Hauptmenü ====="
echo "1) Systemverwaltung"
echo "2) Netzwerkverwaltung"
echo "3) Benutzerverwaltung"
echo "0) Beenden"
echo "===================="
read -p "Wähle eine Option: " option
case "$option" in
1) system_menu ;;
2) network_menu ;;
3) user_menu ;;
0) exit 0 ;;
*)
echo "Ungültige Option!"
read -n 1 -p "Drücke eine Taste, um fortzufahren..."
main_menu
;;
esac
}
# Untermenü für Systemverwaltung
system_menu() {
clear
echo "===== Systemverwaltung ====="
echo "1) Systemstatus anzeigen"
echo "2) Festplattenbelegung prüfen"
echo "3) Prozesse anzeigen"
echo "9) Zurück zum Hauptmenü"
echo "0) Beenden"
echo "==========================="
read -p "Wähle eine Option: " option
case "$option" in
1)
uptime
free -h
read -n 1 -p "Drücke eine Taste, um fortzufahren..."
system_menu
;;
2)
df -h
read -n 1 -p "Drücke eine Taste, um fortzufahren..."
system_menu
;;
3)
ps aux | head -20
read -n 1 -p "Drücke eine Taste, um fortzufahren..."
system_menu
;;
9) main_menu ;;
0) exit 0 ;;
*)
echo "Ungültige Option!"
read -n 1 -p "Drücke eine Taste, um fortzufahren..."
system_menu
;;
esac
}
# Weitere Untermenüs können hier definiert werden
network_menu() {
# Ähnlich wie system_menu
echo "Netzwerkverwaltung noch nicht implementiert."
read -n 1 -p "Drücke eine Taste, um fortzufahren..."
main_menu
}
user_menu() {
# Ähnlich wie system_menu
echo "Benutzerverwaltung noch nicht implementiert."
read -n 1 -p "Drücke eine Taste, um fortzufahren..."
main_menu
}
# Start des Programms
main_menuselectBash bietet den select-Befehl, der automatisch
nummerierte Menüs generiert:
#!/bin/bash
echo "Wähle ein Betriebssystem:"
select os in "Linux" "Windows" "macOS" "FreeBSD" "Andere"; do
case $os in
"Linux")
echo "Du hast Linux gewählt."
break
;;
"Windows")
echo "Du hast Windows gewählt."
break
;;
"macOS")
echo "Du hast macOS gewählt."
break
;;
"FreeBSD")
echo "Du hast FreeBSD gewählt."
break
;;
"Andere")
echo "Du hast eine andere Option gewählt."
break
;;
*)
echo "Ungültige Auswahl. Bitte wähle 1-5."
;;
esac
done
# select mit dynamischen Optionen aus einem Array
distros=("Ubuntu" "Fedora" "Debian" "Arch" "CentOS")
echo "Wähle eine Linux-Distribution:"
select distro in "${distros[@]}" "Zurück"; do
if [[ "$distro" == "Zurück" ]]; then
break
elif [[ -n "$distro" ]]; then
echo "Du hast $distro gewählt."
break
else
echo "Ungültige Auswahl. Bitte versuche es erneut."
fi
doneFür anspruchsvollere interaktive Skripte können Sie die ANSI-Escape-Sequenzen nutzen, um die Terminalanzeige zu steuern:
#!/bin/bash
# Terminalfarben und -formatierung
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color (Zurücksetzen)
# Terminalsteuerung
clear_screen() { echo -e "\033[2J\033[H"; }
move_cursor() { echo -e "\033[$1;${2}H"; }
hide_cursor() { echo -e "\033[?25l"; }
show_cursor() { echo -e "\033[?25h"; }
# Beispiel für ein fortgeschrittenes Menü mit Farbakzenten
advanced_menu() {
clear_screen
echo -e "${BLUE}==============================${NC}"
echo -e "${YELLOW} SYSTEMVERWALTUNG ${NC}"
echo -e "${BLUE}==============================${NC}"
echo
echo -e " ${GREEN}1)${NC} Systemstatus anzeigen"
echo -e " ${GREEN}2)${NC} Festplattenbelegung prüfen"
echo -e " ${GREEN}3)${NC} Benutzer verwalten"
echo -e " ${GREEN}4)${NC} Dienste verwalten"
echo -e " ${RED}0)${NC} Beenden"
echo
echo -e "${BLUE}------------------------------${NC}"
read -p "Wähle eine Option: " option
# Verarbeite die Option wie im vorherigen Beispiel
}
# Fortschrittsanzeige
progress_bar() {
local duration=$1
local steps=20
local delay=$(echo "scale=3; $duration / $steps" | bc)
echo -ne "${YELLOW}Verarbeitung: ${NC}"
hide_cursor
for ((i=0; i<=$steps; i++)); do
local percentage=$((i * 100 / steps))
local completed=$((i * steps / steps))
local remaining=$((steps - completed))
echo -ne "\r${YELLOW}Verarbeitung: ${NC}["
echo -ne "${GREEN}"
for ((j=0; j<completed; j++)); do echo -ne "#"; done
echo -ne "${NC}"
for ((j=0; j<remaining; j++)); do echo -ne "."; done
echo -ne "] ${BLUE}${percentage}%${NC}"
sleep $delay
done
echo -e "\r${YELLOW}Verarbeitung: ${GREEN}[####################] ${BLUE}100%${NC}"
show_cursor
}
# Benutzung der Fortschrittsanzeige
echo "Starte Verarbeitung..."
progress_bar 5 # 5 Sekunden Dauer
echo -e "\n${GREEN}Verarbeitung abgeschlossen!${NC}"Sie können auch einzelne Tastenanschläge ohne Enter abfangen:
#!/bin/bash
# Funktion zum Erfassen eines einzelnen Zeichens ohne Enter
get_char() {
IFS= read -r -s -n 1 char
echo "$char"
}
# Pfeilnavigation
echo "Verwende die Pfeiltasten zum Navigieren (q zum Beenden):"
selected=1
max_options=4
while true; do
# Menü anzeigen
clear
echo "Menüoptionen:"
for i in $(seq 1 $max_options); do
if [ $i -eq $selected ]; then
echo -e " > Option $i"
else
echo -e " Option $i"
fi
done
# Taste abfangen
char=$(get_char)
case "$char" in
A) # Pfeil nach oben
((selected--))
if [ $selected -lt 1 ]; then
selected=$max_options
fi
;;
B) # Pfeil nach unten
((selected++))
if [ $selected -gt $max_options ]; then
selected=1
fi
;;
q|Q)
echo "Beenden..."
exit 0
;;
"")
echo "Option $selected ausgewählt!"
read -n 1 -p "Drücke eine Taste, um fortzufahren..."
;;
esac
doneFür komplexe Konfigurationen können Sie einen mehrstufigen Wizard implementieren:
#!/bin/bash
# Arrays für die Konfigurationsdaten
declare -A config
# Farben
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m'
# Wizard-Header
show_header() {
clear
echo -e "${BLUE}===================================${NC}"
echo -e "${BLUE} Webserver-Konfigurationswizard ${NC}"
echo -e "${BLUE}===================================${NC}"
echo
echo -e "Schritt $1 von 4: $2"
echo
}
# Schritt 1: Grundlegende Einstellungen
step_basic_settings() {
show_header "1" "Grundlegende Einstellungen"
read -p "Servername [localhost]: " config[server_name]
config[server_name]=${config[server_name]:-localhost}
read -p "Port [80]: " config[port]
config[port]=${config[port]:-80}
read -p "Dokumentenverzeichnis [/var/www/html]: " config[doc_root]
config[doc_root]=${config[doc_root]:-/var/www/html}
read -p "Weiter zum nächsten Schritt? (j/n) [j]: " next
next=${next:-j}
if [[ "$next" =~ ^[jJ]$ ]]; then
step_ssl_settings
else
step_basic_settings
fi
}
# Schritt 2: SSL-Einstellungen
step_ssl_settings() {
show_header "2" "SSL-Einstellungen"
read -p "SSL aktivieren? (j/n) [n]: " ssl_enabled
ssl_enabled=${ssl_enabled:-n}
if [[ "$ssl_enabled" =~ ^[jJ]$ ]]; then
config[ssl_enabled]=true
read -p "SSL-Zertifikatspfad: " config[ssl_cert]
read -p "SSL-Schlüsselpfad: " config[ssl_key]
else
config[ssl_enabled]=false
fi
read -p "Weiter zum nächsten Schritt? (j/n) [j]: " next
next=${next:-j}
if [[ "$next" =~ ^[jJ]$ ]]; then
step_advanced_settings
else
step_ssl_settings
fi
}
# Schritt 3: Erweiterte Einstellungen
step_advanced_settings() {
show_header "3" "Erweiterte Einstellungen"
read -p "Maximale Verbindungen [150]: " config[max_connections]
config[max_connections]=${config[max_connections]:-150}
read -p "Timeout in Sekunden [60]: " config[timeout]
config[timeout]=${config[timeout]:-60}
read -p "Weiter zum nächsten Schritt? (j/n) [j]: " next
next=${next:-j}
if [[ "$next" =~ ^[jJ]$ ]]; then
step_summary
else
step_advanced_settings
fi
}
# Schritt 4: Zusammenfassung und Bestätigung
step_summary() {
show_header "4" "Zusammenfassung und Bestätigung"
echo "Konfigurationsübersicht:"
echo "-----------------------"
echo "Servername: ${config[server_name]}"
echo "Port: ${config[port]}"
echo "Dokumentenverzeichnis: ${config[doc_root]}"
echo "SSL aktiviert: ${config[ssl_enabled]}"
if [[ "${config[ssl_enabled]}" == "true" ]]; then
echo "SSL-Zertifikat: ${config[ssl_cert]}"
echo "SSL-Schlüssel: ${config[ssl_key]}"
fi
echo "Maximale Verbindungen: ${config[max_connections]}"
echo "Timeout: ${config[timeout]} Sekunden"
echo "-----------------------"
read -p "Konfiguration anwenden? (j/n) [j]: " apply
apply=${apply:-j}
if [[ "$apply" =~ ^[jJ]$ ]]; then
apply_configuration
else
echo "Konfiguration abgebrochen."
exit 0
fi
}
# Konfiguration anwenden
apply_configuration() {
echo
echo -e "${GREEN}Konfiguration wird angewendet...${NC}"
# Hier würde die tatsächliche Konfigurationserstellung stattfinden
# Zum Beispiel:
#
# cat > webserver.conf << EOF
# server {
# listen ${config[port]};
# server_name ${config[server_name]};
# root ${config[doc_root]};
# ...
# }
# EOF
# Hier nur eine Simulation:
echo "Erstelle Konfigurationsdatei..."
sleep 1
echo "Überprüfe Konfigurationsdatei..."
sleep 1
echo "Starte Webserver neu..."
sleep 2
echo -e "${GREEN}Konfiguration erfolgreich angewendet!${NC}"
}
# Wizard starten
step_basic_settingsBei fortgeschrittenen interaktiven Skripten können Sie auch direkt mit Tastatursignalen arbeiten:
#!/bin/bash
# Terminal in den "raw" Modus versetzen
old_stty_settings=$(stty -g)
stty raw -echo
# Bei Beendigung des Skripts Terminal zurücksetzen
trap 'stty "$old_stty_settings"; echo; exit 0' EXIT INT TERM
echo "Verwende die WASD-Tasten zum Bewegen (q zum Beenden):"
# Spielerposition
x=10
y=5
# Bildschirm aktualisieren
update_screen() {
clear
# Spielfeld zeichnen
for ((i=1; i<=20; i++)); do
for ((j=1; j<=40; j++)); do
if [ "$i" -eq "$y" ] && [ "$j" -eq "$x" ]; then
echo -n "X"
else
echo -n "."
fi
done
echo
done
echo "Position: $x,$y"
}
# Hauptschleife
while true; do
update_screen
# Taste lesen
read -r -n 1 key
# Taste verarbeiten
case "$key" in
w|W) ((y--)) ;;
a|A) ((x--)) ;;
s|S) ((y++)) ;;
d|D) ((x++)) ;;
q|Q) break ;;
esac
# Grenzen einhalten
if [ "$x" -lt 1 ]; then x=1; fi
if [ "$x" -gt 40 ]; then x=40; fi
if [ "$y" -lt 1 ]; then y=1; fi
if [ "$y" -gt 20 ]; then y=20; fi
doneSelbst in textbasierten Terminals können Sie bestimmte grafische Elemente simulieren:
#!/bin/bash
# Funktion zum Zeichnen eines Rahmens
draw_box() {
local width=$1
local height=$2
local title=$3
# Obere Kante
echo -n "┌"
printf "─%.0s" $(seq 1 $((width-2)))
echo "┐"
# Titelzeile, wenn vorhanden
if [ -n "$title" ]; then
local padding=$(( (width - ${#title} - 2) / 2 ))
echo -n "│"
printf " %.0s" $(seq 1 $padding)
echo -n "$title"
printf " %.0s" $(seq 1 $((width - ${#title} - 2 - padding)))
echo "│"
# Trennlinie unter dem Titel
echo -n "├"
printf "─%.0s" $(seq 1 $((width-2)))
echo "┤"
fi
# Inhalt (leer)
for ((i=0; i<height-2-(title?1:0); i++)); do
echo -n "│"
printf " %.0s" $(seq 1 $((width-2)))
echo "│"
done
# Untere Kante
echo -n "└"
printf "─%.0s" $(seq 1 $((width-2)))
echo "┘"
}
# Funktion für Radiobuttons
radio_buttons() {
local options=("$@")
local selected=0
while true; do
clear
echo "Wähle eine Option (↑/↓ zum Navigieren, Enter zum Auswählen, q zum Beenden):"
echo
for i in "${!options[@]}"; do
if [ "$i" -eq "$selected" ]; then
echo -e " (●) ${options[$i]}"
else
echo -e " (○) ${options[$i]}"
fi
done
IFS= read -r -s -n 1 key
case "$key" in
A) # Pfeil nach oben
((selected--))
if [ "$selected" -lt 0 ]; then
selected=$((${#options[@]}-1))
fi
;;
B) # Pfeil nach unten
((selected++))
if [ "$selected" -ge "${#options[@]}" ]; then
selected=0
fi
;;
q|Q)
return 255
;;
"")
return "$selected"
;;
esac
done
}
# Funktion für Checkboxen
checkboxes() {
local options=("$@")
local selected=0
local -a checked
# Alle Checkboxen initial deaktivieren
for i in "${!options[@]}"; do
checked[$i]=0
done
while true; do
clear
echo "Wähle Optionen (↑/↓ zum Navigieren, Leertaste zum Umschalten, Enter zum Bestätigen):"
echo
for i in "${!options[@]}"; do
if [ "$i" -eq "$selected" ]; then
if [ "${checked[$i]}" -eq 1 ]; then
echo -e " ▶[✓] ${options[$i]}"
else
echo -e " ▶[ ] ${options[$i]}"
fi
else
if [ "${checked[$i]}" -eq 1 ]; then
echo -e " [✓] ${options[$i]}"
else
echo -e " [ ] ${options[$i]}"
fi
fi
done
IFS= read -r -s -n 1 key
case "$key" in
A) # Pfeil nach oben
((selected--))
if [ "$selected" -lt 0 ]; then
selected=$((${#options[@]}-1))
fi
;;
B) # Pfeil nach unten
((selected++))
if [ "$selected" -ge "${#options[@]}" ]; then
selected=0
fi
;;
" ") # Leertaste zum Umschalten
if [ "${checked[$selected]}" -eq 0 ]; then
checked[$selected]=1
else
checked[$selected]=0
fi
;;
q|Q)
return 255
;;
"")
# Gib ausgewählte Optionen zurück
local result=""
for i in "${!options[@]}"; do
if [ "${checked[$i]}" -eq 1 ]; then
result+="${options[$i]},"
fi
done
echo "${result%,}" # Entferne das letzte Komma
return 0
;;
esac
done
}
# Beispielverwendung
draw_box 50 10 "Konfigurationsassistent"
echo "Wähle dein Betriebssystem:"
radio_buttons "Linux" "Windows" "macOS" "FreeBSD" "Anderes"
os_choice=$?
os_name="${@:1:$((os_choice+1)):1}"
echo "Du hast $os_name gewählt."
echo -e "\nWähle die zu installierenden Komponenten:"
components=$(checkboxes "Webserver" "Datenbank" "PHP" "Mail" "Proxy")
echo "Du hast folgende Komponenten gewählt: $components"Die Fähigkeit, das Terminal zu steuern und farbige Ausgaben zu erzeugen, kann Shell-Skripte erheblich aufwerten. Mit diesen Techniken können Sie die Benutzerfreundlichkeit verbessern, wichtige Informationen hervorheben und komplexere textbasierte Benutzeroberflächen erstellen. In diesem Abschnitt werden wir verschiedene Methoden untersuchen, um das Terminal zu steuern und farbige, formatierte Ausgaben zu erzeugen.
ANSI-Escape-Sequenzen sind die Basis für Terminalsteuerung und
Farbausgabe. Diese speziellen Zeichenfolgen beginnen mit dem
Escape-Zeichen (\033 oder \e) und werden vom
Terminal interpretiert, um verschiedene Formatierungen zu erzeugen oder
Steuerungsbefehle auszuführen.
Die grundlegende Syntax einer ANSI-Escape-Sequenz ist:
\033[Parameter;Parameter;...m
Hier sind die häufigsten Formatierungsparameter:
#!/bin/bash
# Textformatierung
echo -e "\033[1mFettgedruckter Text\033[0m"
echo -e "\033[3mKursiver Text\033[0m" # Nicht von allen Terminals unterstützt
echo -e "\033[4mUnterstrichener Text\033[0m"
echo -e "\033[9mDurchgestrichener Text\033[0m" # Nicht von allen Terminals unterstützt
# Zurücksetzen aller Formatierungen
echo -e "Normaler Text \033[1mFettgedruckt\033[0m Wieder normal"Hier sind die gängigsten Farbcodes:
#!/bin/bash
# Vordergrundfarben (Text)
echo -e "\033[30mSchwarz\033[0m"
echo -e "\033[31mRot\033[0m"
echo -e "\033[32mGrün\033[0m"
echo -e "\033[33mGelb\033[0m"
echo -e "\033[34mBlau\033[0m"
echo -e "\033[35mMagenta\033[0m"
echo -e "\033[36mCyan\033[0m"
echo -e "\033[37mWeiß\033[0m"
# Helle Vordergrundfarben
echo -e "\033[90mHelles Schwarz (Grau)\033[0m"
echo -e "\033[91mHelles Rot\033[0m"
echo -e "\033[92mHelles Grün\033[0m"
echo -e "\033[93mHelles Gelb\033[0m"
echo -e "\033[94mHelles Blau\033[0m"
echo -e "\033[95mHelles Magenta\033[0m"
echo -e "\033[96mHelles Cyan\033[0m"
echo -e "\033[97mHelles Weiß\033[0m"
# Hintergrundfarben
echo -e "\033[40mSchwarz\033[0m"
echo -e "\033[41mRot\033[0m"
echo -e "\033[42mGrün\033[0m"
echo -e "\033[43mGelb\033[0m"
echo -e "\033[44mBlau\033[0m"
echo -e "\033[45mMagenta\033[0m"
echo -e "\033[46mCyan\033[0m"
echo -e "\033[47mWeiß\033[0m"
# Helle Hintergrundfarben
echo -e "\033[100mHelles Schwarz (Grau)\033[0m"
echo -e "\033[101mHelles Rot\033[0m"
echo -e "\033[102mHelles Grün\033[0m"
echo -e "\033[103mHelles Gelb\033[0m"
echo -e "\033[104mHelles Blau\033[0m"
echo -e "\033[105mHelles Magenta\033[0m"
echo -e "\033[106mHelles Cyan\033[0m"
echo -e "\033[107mHelles Weiß\033[0m"Sie können mehrere Formatierungen in einer Sequenz kombinieren:
# Kombinierte Formatierungen
echo -e "\033[1;31mFettgedruckter roter Text\033[0m"
echo -e "\033[4;34;43mUnterstrichener blauer Text auf gelbem Hintergrund\033[0m"
# Komplexes Beispiel
echo -e "\033[1;37;44mWeiß, fett auf blauem Hintergrund\033[0m \033[4;31mRot unterstrichen\033[0m"Die Verwendung von Variablen für Farbcodes verbessert die Lesbarkeit Ihrer Skripte erheblich:
#!/bin/bash
# Farbdefinitionen
RESET="\033[0m"
BOLD="\033[1m"
UNDERLINE="\033[4m"
BLACK="\033[30m"
RED="\033[31m"
GREEN="\033[32m"
YELLOW="\033[33m"
BLUE="\033[34m"
MAGENTA="\033[35m"
CYAN="\033[36m"
WHITE="\033[37m"
BG_BLACK="\033[40m"
BG_RED="\033[41m"
BG_GREEN="\033[42m"
BG_YELLOW="\033[43m"
BG_BLUE="\033[44m"
BG_MAGENTA="\033[45m"
BG_CYAN="\033[46m"
BG_WHITE="\033[47m"
# Verwendung
echo -e "${RED}Fehler:${RESET} Datei nicht gefunden."
echo -e "${GREEN}Erfolg:${RESET} Operation abgeschlossen."
echo -e "${YELLOW}Warnung:${RESET} Festplatte fast voll."
echo -e "${BOLD}${BLUE}Information:${RESET} System bereit."Eine alternative und portablere Methode zur Terminalsteuerung ist die
Verwendung des tput-Befehls, der die Termcap- oder
Terminfo-Datenbank des Systems nutzt:
#!/bin/bash
# Farbdefinitionen mit tput
RESET=$(tput sgr0)
BOLD=$(tput bold)
UNDERLINE=$(tput smul)
REVERSE=$(tput rev)
BLACK=$(tput setaf 0)
RED=$(tput setaf 1)
GREEN=$(tput setaf 2)
YELLOW=$(tput setaf 3)
BLUE=$(tput setaf 4)
MAGENTA=$(tput setaf 5)
CYAN=$(tput setaf 6)
WHITE=$(tput setaf 7)
BG_BLACK=$(tput setab 0)
BG_RED=$(tput setab 1)
BG_GREEN=$(tput setab 2)
BG_YELLOW=$(tput setab 3)
BG_BLUE=$(tput setab 4)
BG_MAGENTA=$(tput setab 5)
BG_CYAN=$(tput setab 6)
BG_WHITE=$(tput setab 7)
# Verwendung
echo "${RED}Fehler:${RESET} Datei nicht gefunden."
echo "${GREEN}Erfolg:${RESET} Operation abgeschlossen."
echo "${YELLOW}Warnung:${RESET} Festplatte fast voll."
echo "${BOLD}${BLUE}Information:${RESET} System bereit."
# Terminalbreite und -höhe ermitteln
COLUMNS=$(tput cols)
LINES=$(tput lines)
echo "Terminal-Größe: ${COLUMNS}x${LINES} Zeichen"Der Vorteil von tput ist, dass es die Fähigkeiten des
aktuellen Terminals erkennt und entsprechend anpasst, was die
Portabilität Ihrer Skripte verbessert.
Neben Farbformatierungen können Sie das Terminal auch steuern, um den Cursor zu bewegen, den Bildschirm zu löschen und mehr:
#!/bin/bash
# Bildschirm löschen (Alternative zu 'clear')
clear_screen() {
echo -e "\033[2J\033[H"
# oder mit tput:
# tput clear
}
# Cursor an Position x,y bewegen
cursor_position() {
echo -e "\033[${2};${1}H"
# oder mit tput:
# tput cup $2 $1
}
# Aktuelle Zeile löschen
clear_line() {
echo -e "\033[2K"
# oder mit tput:
# tput el
}
# Cursor unsichtbar machen
hide_cursor() {
echo -e "\033[?25l"
# oder mit tput:
# tput civis
}
# Cursor sichtbar machen
show_cursor() {
echo -e "\033[?25h"
# oder mit tput:
# tput cnorm
}
# Text blinken lassen (wird nicht von allen Terminals unterstützt)
blink_text() {
echo -e "\033[5m$1\033[0m"
}
# Den gesamten Bildschirm speichern und wiederherstellen
save_screen() {
echo -e "\033[?47h"
}
restore_screen() {
echo -e "\033[?47l"
}
# Beispielverwendung
clear_screen
cursor_position 10 5
echo "Text an Position 10,5"
sleep 2
hide_cursor
for i in {1..10}; do
cursor_position 1 10
clear_line
echo -n "Zähler: $i"
sleep 0.5
done
show_cursor
cursor_position 1 12
echo "Fertig!"#!/bin/bash
# Farbdefinitionen
RESET="\033[0m"
RED="\033[31m"
GREEN="\033[32m"
YELLOW="\033[33m"
BLUE="\033[34m"
# Funktionen für Statusmeldungen
log_error() {
echo -e "${RED}[FEHLER]${RESET} $1"
}
log_success() {
echo -e "${GREEN}[ERFOLG]${RESET} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNUNG]${RESET} $1"
}
log_info() {
echo -e "${BLUE}[INFO]${RESET} $1"
}
# Beispielverwendung
log_info "Starte Skript..."
log_warning "Festplattenplatz niedrig"
log_error "Datei konnte nicht geöffnet werden"
log_success "Datenbank erfolgreich gesichert"#!/bin/bash
# Funktion zum Hervorheben von Suchbegriffen
highlight_matches() {
local search_term="$1"
local file="$2"
local YELLOW="\033[1;33m"
local RESET="\033[0m"
if [ -f "$file" ]; then
cat "$file" | sed "s/$search_term/${YELLOW}$search_term${RESET}/gi"
else
echo "Datei nicht gefunden: $file"
fi
}
# Beispielverwendung
echo "Suche nach 'error' in der Logdatei:"
highlight_matches "error" "/var/log/syslog"#!/bin/bash
# Farbdefinitionen
RESET="\033[0m"
GREEN="\033[32m"
YELLOW="\033[33m"
BLUE="\033[34m"
# Fortschrittsbalken anzeigen
show_progress() {
local total=$1
local current=$2
local bar_length=50
local filled_length=$((current * bar_length / total))
# Prozentberechnung
local percent=$((current * 100 / total))
# Fortschrittsbalken erstellen
local bar=""
for ((i=0; i<filled_length; i++)); do
bar="${bar}█"
done
for ((i=filled_length; i<bar_length; i++)); do
bar="${bar}░"
done
# Cursor an den Anfang der Zeile bewegen und Zeile löschen
echo -ne "\r\033[K"
# Fortschrittsbalken mit Farben ausgeben
echo -ne "${YELLOW}[${GREEN}${bar}${YELLOW}] ${BLUE}${percent}%${RESET}"
# Wenn fertig, neue Zeile hinzufügen
if [ "$current" -eq "$total" ]; then
echo
fi
}
# Beispielverwendung - Simulation eines längeren Prozesses
total_steps=100
for ((i=0; i<=total_steps; i++)); do
show_progress $total_steps $i
sleep 0.05
done
echo -e "${GREEN}Prozess abgeschlossen!${RESET}"#!/bin/bash
# Farbdefinitionen
RESET="\033[0m"
BOLD="\033[1m"
BLACK="\033[30m"
RED="\033[31m"
GREEN="\033[32m"
YELLOW="\033[33m"
BLUE="\033[34m"
MAGENTA="\033[35m"
CYAN="\033[36m"
WHITE="\033[37m"
BG_BLACK="\033[40m"
BG_RED="\033[41m"
BG_GREEN="\033[42m"
BG_YELLOW="\033[43m"
BG_BLUE="\033[44m"
BG_MAGENTA="\033[45m"
BG_CYAN="\033[46m"
BG_WHITE="\033[47m"
# Cursor unsichtbar machen
echo -e "\033[?25l"
# Bei Beendigung des Skripts Cursor wieder sichtbar machen
trap 'echo -e "\033[?25h"; exit 0' SIGINT SIGTERM EXIT
# Menü anzeigen
selected=0
options=("System-Info anzeigen" "Festplattennutzung prüfen" "Prozesse auflisten" "Beenden")
draw_menu() {
clear
echo -e "${BOLD}${BG_BLUE}${WHITE} Hauptmenü ${RESET}"
echo
for i in "${!options[@]}"; do
if [ "$i" -eq "$selected" ]; then
echo -e " ${BG_CYAN}${BLACK} $(($i+1)) ${RESET} ${BOLD}${YELLOW}${options[$i]}${RESET}"
else
echo -e " ${BG_BLACK}${WHITE} $(($i+1)) ${RESET} ${options[$i]}"
fi
done
echo
echo -e "${BLUE}Verwende die Pfeiltasten zum Navigieren und Enter zum Auswählen${RESET}"
}
# Tastatureingabe verarbeiten
get_key() {
IFS= read -r -s -n1 key
case $key in
'A') echo "UP";; # Pfeil nach oben
'B') echo "DOWN";; # Pfeil nach unten
'') echo "ENTER";;
'q'|'Q') echo "QUIT";;
*) ;;
esac
}
# Hauptschleife
while true; do
draw_menu
# Auf Tastendruck warten
read -s -n 1 key
if [[ $key == $'\e' ]]; then
read -s -n 2 key
case $key in
'[A') # Pfeil nach oben
((selected--))
if [ "$selected" -lt 0 ]; then
selected=$((${#options[@]}-1))
fi
;;
'[B') # Pfeil nach unten
((selected++))
if [ "$selected" -ge "${#options[@]}" ]; then
selected=0
fi
;;
esac
elif [[ $key == "" ]]; then # Enter-Taste
case $selected in
0)
clear
echo -e "${BOLD}${GREEN}=== Systeminformationen ===${RESET}\n"
echo -e "${CYAN}Hostname:${RESET} $(hostname)"
echo -e "${CYAN}Kernel:${RESET} $(uname -r)"
echo -e "${CYAN}Uptime:${RESET} $(uptime -p)"
echo -e "${CYAN}CPU:${RESET} $(grep 'model name' /proc/cpuinfo | head -1 | cut -d':' -f2 | sed 's/^[ \t]*//')"
echo -e "${CYAN}Arbeitsspeicher:${RESET}"
free -h
echo
read -n 1 -s -r -p "Drücke eine Taste, um fortzufahren..."
;;
1)
clear
echo -e "${BOLD}${GREEN}=== Festplattennutzung ===${RESET}\n"
df -h | awk '{print $1, $2, $3, $4, $5, $6}' | grep -v "tmpfs" |
while read fs size used avail use mount; do
if [ "$fs" = "Filesystem" ]; then
echo -e "${BOLD}${fs} ${size} ${used} ${avail} ${use} ${mount}${RESET}"
else
use_num=$(echo $use | sed 's/%//')
if [ "$use_num" -ge 90 ]; then
color=$RED
elif [ "$use_num" -ge 70 ]; then
color=$YELLOW
else
color=$GREEN
fi
echo -e "${fs} ${size} ${used} ${avail} ${color}${use}${RESET} ${mount}"
fi
done
echo
read -n 1 -s -r -p "Drücke eine Taste, um fortzufahren..."
;;
2)
clear
echo -e "${BOLD}${GREEN}=== Prozessliste ===${RESET}\n"
ps aux | head -1 | awk '{print $1, $2, $3, $4, $11}' |
while read user pid cpu mem cmd; do
echo -e "${BOLD}${user} ${pid} ${cpu} ${mem} ${cmd}${RESET}"
done
ps aux | grep -v "USER" | sort -rn -k 3 | head -10 | awk '{print $1, $2, $3, $4, $11}' |
while read user pid cpu mem cmd; do
cpu_num=$(echo $cpu | sed 's/,/./') # Für Lokalisierungen, die Komma verwenden
if (( $(echo "$cpu_num > 10.0" | bc -l) )); then
color=$RED
elif (( $(echo "$cpu_num > 5.0" | bc -l) )); then
color=$YELLOW
else
color=$GREEN
fi
echo -e "${user} ${pid} ${color}${cpu}${RESET} ${mem} ${cmd}"
done
echo
read -n 1 -s -r -p "Drücke eine Taste, um fortzufahren..."
;;
3)
echo -e "\n${BOLD}${GREEN}Programm wird beendet...${RESET}"
sleep 1
exit 0
;;
esac
elif [[ $key == "q" || $key == "Q" ]]; then
echo -e "\n${BOLD}${GREEN}Programm wird beendet...${RESET}"
sleep 1
exit 0
fi
doneModerne Terminals unterstützen über die grundlegenden 16 Farben hinaus auch 256 Farben oder sogar True Color (16 Millionen Farben):
#!/bin/bash
# 256-Farben-Palette anzeigen
show_256_colors() {
echo "256-Farben-Palette:"
for i in {0..255}; do
printf "\033[38;5;${i}m%3d\033[0m " $i
if (( (i+1) % 16 == 0 )); then
echo
fi
done
echo
}
# Eine einzelne 256-Farbe verwenden
use_256_color() {
local color_code=$1
local text=$2
echo -e "\033[38;5;${color_code}m${text}\033[0m"
}
# True Color (24-bit) verwenden
use_true_color() {
local r=$1
local g=$2
local b=$3
local text=$4
echo -e "\033[38;2;${r};${g};${b}m${text}\033[0m"
}
# Beispiele
show_256_colors
echo
echo "Beispiele für 256-Farben:"
use_256_color 196 "Helles Rot (196)"
use_256_color 46 "Helles Grün (46)"
use_256_color 27 "Blau (27)"
use_256_color 208 "Orange (208)"
use_256_color 165 "Magenta (165)"
echo
echo "Beispiele für True Color:"
use_true_color 255 100 100 "Pastellrot (255,100,100)"
use_true_color 100 255 100 "Pastellgrün (100,255,100)"
use_true_color 100 100 255 "Pastellblau (100,100,255)"
use_true_color 255 165 0 "Orange (255,165,0)"
use_true_color 128 0 128 "Lila (128,0,128)"
# Farbverlauf mit True Color
echo
echo "Farbverlauf mit True Color:"
for i in {0..50}; do
r=$((255 - i * 5))
g=$((i * 5))
b=100
use_true_color $r $g $b "#"
done
echoMit Unicode-Box-Zeichenzeichen können Sie ansprechende Rahmen und Kästen erstellen:
#!/bin/bash
# Farbdefinitionen
RESET="\033[0m"
RED="\033[31m"
GREEN="\033[32m"
YELLOW="\033[33m"
BLUE="\033[34m"
MAGENTA="\033[35m"
CYAN="\033[36m"
# Funktion zum Zeichnen einer Box
draw_box() {
local width=$1
local title=$2
local color=$3
local content=$4
# Obere Kante
echo -e "${color}┌$( printf '─%.0s' $(seq 1 $((width-2))) )┐${RESET}"
# Titel, falls vorhanden
if [ -n "$title" ]; then
local padding=$(( (width - ${#title} - 2) / 2 ))
local extra_padding=$(( (width - ${#title} - 2) % 2 ))
echo -en "${color}│${RESET}"
printf " %.0s" $(seq 1 $padding)
echo -en "${YELLOW}${title}${RESET}"
printf " %.0s" $(seq 1 $((padding + extra_padding)))
echo -e "${color}│${RESET}"
# Trennlinie unter dem Titel
echo -e "${color}├$( printf '─%.0s' $(seq 1 $((width-2))) )┤${RESET}"
fi
# Inhalt, Zeile für Zeile
IFS=$'\n'
for line in $content; do
local line_padding=$(( width - ${#line} - 2 ))
echo -en "${color}│${RESET} "
echo -n "$line"
printf " %.0s" $(seq 1 $line_padding)
echo -e "${color}│${RESET}"
done
# Untere Kante
echo -e "${color}└$( printf '─%.0s' $(seq 1 $((width-2))) )┘${RESET}"
}
# Beispielverwendung
system_info=$(cat << EOF
Hostname: $(hostname)
Kernel: $(uname -r)
Uptime: $(uptime -p)
CPU: $(grep -m1 'model name' /proc/cpuinfo | cut -d':' -f2 | sed 's/^[ \t]*//')
EOF
)
disk_info=$(df -h / /home | grep -v "Filesystem")
draw_box 60 "Systeminformationen" $BLUE "$system_info"
echo
draw_box 60 "Festplattennutzung" $GREEN "$disk_info"
echo
# Mehrere Boxen nebeneinander
cpu_info="CPU: $(grep -m1 'model name' /proc/cpuinfo | cut -d':' -f2 | sed 's/^[ \t]*//')"
mem_info="Speicher: $(free -h | grep Mem | awk '{print $3 " / " $2 " verwendet"}')"
echo -en "${RED}┌───────────────────────────┐${RESET} "
echo -e "${CYAN}┌───────────────────────────┐${RESET}"
echo -en "${RED}│${RESET} ${YELLOW}CPU-Information${RESET} ${RED}│${RESET} "
echo -e "${CYAN}│${RESET} ${YELLOW}Speicher-Information${RESET} ${CYAN}│${RESET}"
echo -en "${RED}├───────────────────────────┤${RESET} "
echo -e "${CYAN}├───────────────────────────┤${RESET}"
echo -en "${RED}│${RESET} $cpu_info ${RED}│${RESET} "
echo -e "${CYAN}│${RESET} $mem_info ${CYAN}│${RESET}"
echo -en "${RED}└───────────────────────────┘${RESET} "
echo -e "${CYAN}└───────────────────────────┘${RESET}"Animierte Statusanzeigen können die Benutzerfreundlichkeit bei länger laufenden Operationen verbessern:
#!/bin/bash
# Farbdefinitionen
RESET="\033[0m"
GREEN="\033[32m"
YELLOW="\033[33m"
BLUE="\033[34m"
MAGENTA="\033[35m"
CYAN="\033[36m"
# Cursor unsichtbar machen
hide_cursor() {
echo -ne "\033[?25l"
}
# Cursor sichtbar machen
show_cursor() {
echo -ne "\033[?25h"
}
# Bei Beendigung des Skripts Cursor wieder sichtbar machen
trap show_cursor EXIT INT TERM
# Einfacher animierter Spinner
spinner() {
local pid=$1
local delay=0.1
local spinstr='|/-\'
hide_cursor
while ps -p $pid > /dev/null; do
for i in $(seq 0 3); do
echo -ne "\r[${spinstr:$i:1}] Läuft..."
sleep $delay
done
done
echo -e "\r[${GREEN}✓${RESET}] Abgeschlossen! "
show_cursor
}
# Spinner mit Farbrotation
fancy_spinner() {
local pid=$1
local msg=$2
local delay=0.1
local spinstr='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
local colors=("\033[31m" "\033[33m" "\033[32m" "\033[36m" "\033[34m" "\033[35m")
hide_cursor
while ps -p $pid > /dev/null; do
for i in $(seq 0 9); do
color_index=$((i % 6))
echo -ne "\r${colors[$color_index]}${spinstr:$i:1}${RESET} $msg"
sleep $delay
done
done
echo -e "\r${GREEN}✓${RESET} $msg - Abgeschlossen!"
show_cursor
}
# Fortschrittsbalken mit Prozentanzeige
progress_bar() {
local pid=$1
local msg=$2
local total_time=$3 # Geschätzte Gesamtzeit in Sekunden
local start_time=$(date +%s)
local bar_length=30
hide_cursor
while ps -p $pid > /dev/null; do
local current_time=$(date +%s)
local elapsed=$((current_time - start_time))
if [ $elapsed -gt $total_time ]; then
local percent=99
else
local percent=$((elapsed * 100 / total_time))
fi
local filled_length=$((percent * bar_length / 100))
local empty_length=$((bar_length - filled_length))
local bar=""
for ((i=0; i<filled_length; i++)); do
bar="${bar}█"
done
for ((i=0; i<empty_length; i++)); do
bar="${bar}░"
done
echo -ne "\r${BLUE}[$YELLOW$bar${BLUE}] ${GREEN}${percent}%${RESET} $msg"
sleep 0.2
done
echo -ne "\r${BLUE}[${YELLOW}"
for ((i=0; i<bar_length; i++)); do
echo -n "█"
done
echo -e "${BLUE}] ${GREEN}100%${RESET} $msg - Abgeschlossen!"
show_cursor
}
# Beispielverwendung
echo "Starte langwierigen Prozess..."
# Langwieriger Prozess im Hintergrund starten
{
sleep 5
} &
# Prozess-ID speichern
bg_pid=$!
# Spinner anzeigen, während der Prozess läuft
spinner $bg_pid
echo "Starte Dateianalyse..."
{
sleep 8
} &
bg_pid=$!
# Fancy Spinner anzeigen
fancy_spinner $bg_pid "Analysiere Dateien"
echo "Starte Backup..."
{
sleep 10
} &
bg_pid=$!
# Fortschrittsbalken anzeigen
progress_bar $bg_pid "Sichere Datenbank" 10
echo -e "${GREEN}Alle Aufgaben abgeschlossen!${RESET}"Ein Countdown-Timer kann nützlich sein, um Benutzer über die verbleibende Zeit zu informieren:
#!/bin/bash
# Farbdefinitionen
RESET="\033[0m"
RED="\033[31m"
YELLOW="\033[33m"
GREEN="\033[32m"
# Countdown-Timer mit Farbwechsel
countdown_timer() {
local seconds=$1
local message=$2
for (( i=seconds; i>=0; i-- )); do
# Farbwechsel je nach verbleibender Zeit
if [ $i -gt 10 ]; then
color=$GREEN
elif [ $i -gt 5 ]; then
color=$YELLOW
else
color=$RED
fi
# Sekunden in Minuten:Sekunden umwandeln
local min=$((i / 60))
local sec=$((i % 60))
# Ausgabe mit führenden Nullen für Sekunden unter 10
if [ $sec -lt 10 ]; then
echo -ne "\r${message}: ${color}${min}:0${sec}${RESET} verbleibend"
else
echo -ne "\r${message}: ${color}${min}:${sec}${RESET} verbleibend"
fi
sleep 1
done
echo -e "\r${message}: ${GREEN}Abgeschlossen!${RESET} "
}
# Beispielverwendung
echo "System wird in 30 Sekunden neu gestartet."
countdown_timer 30 "Neustart"
echo "System würde jetzt neu gestartet werden."Für die Darstellung strukturierter Daten können Sie Tabellen mit Rahmen erstellen:
#!/bin/bash
# Farbdefinitionen
RESET="\033[0m"
BOLD="\033[1m"
GREEN="\033[32m"
BLUE="\033[34m"
YELLOW="\033[33m"
# Funktion zur Erstellung einer formatierten Tabelle
print_table() {
local -a header=("${!1}")
local -a data=("${!2}")
local columns=${#header[@]}
local -a column_widths
# Berechnung der Spaltenbreiten
for ((i=0; i<columns; i++)); do
column_widths[$i]=${#header[$i]}
done
# Überprüfe Daten für maximale Spaltenbreite
for ((i=0; i<${#data[@]}; i+=columns)); do
for ((j=0; j<columns; j++)); do
if [ ${#data[$i+$j]} -gt ${column_widths[$j]} ]; then
column_widths[$j]=${#data[$i+$j]}
fi
done
done
# Tabellenkopf zeichnen
echo -en "${BLUE}┌"
for ((i=0; i<columns; i++)); do
printf '─%.0s' $(seq 1 $((column_widths[$i] + 2)))
if [ $i -lt $((columns-1)) ]; then
echo -en "┬"
fi
done
echo -e "┐${RESET}"
# Header ausgeben
echo -en "${BLUE}│${RESET}"
for ((i=0; i<columns; i++)); do
printf " ${BOLD}%-${column_widths[$i]}s${RESET} " "${header[$i]}"
echo -en "${BLUE}│${RESET}"
done
echo
# Trennlinie nach Header
echo -en "${BLUE}├"
for ((i=0; i<columns; i++)); do
printf '─%.0s' $(seq 1 $((column_widths[$i] + 2)))
if [ $i -lt $((columns-1)) ]; then
echo -en "┼"
fi
done
echo -e "┤${RESET}"
# Datenzeilen ausgeben
local rows=$((${#data[@]} / columns))
for ((row=0; row<rows; row++)); do
echo -en "${BLUE}│${RESET}"
for ((col=0; col<columns; col++)); do
printf " %-${column_widths[$col]}s " "${data[$row*$columns+$col]}"
echo -en "${BLUE}│${RESET}"
done
echo
# Trennlinie zwischen Datenzeilen, aber nicht nach der letzten Zeile
if [ $row -lt $((rows-1)) ]; then
echo -en "${BLUE}├"
for ((i=0; i<columns; i++)); do
printf '─%.0s' $(seq 1 $((column_widths[$i] + 2)))
if [ $i -lt $((columns-1)) ]; then
echo -en "┼"
fi
done
echo -e "┤${RESET}"
fi
done
# Untere Kante
echo -en "${BLUE}└"
for ((i=0; i<columns; i++)); do
printf '─%.0s' $(seq 1 $((column_widths[$i] + 2)))
if [ $i -lt $((columns-1)) ]; then
echo -en "┴"
fi
done
echo -e "┘${RESET}"
}
# Beispielverwendung
header=("Hostname" "IP-Adresse" "Status" "Uptime")
data=(
"server1" "192.168.1.101" "${GREEN}Online${RESET}" "45 Tage"
"server2" "192.168.1.102" "${RED}Offline${RESET}" "0 Tage"
"server3" "192.168.1.103" "${GREEN}Online${RESET}" "12 Tage"
"server4" "192.168.1.104" "${YELLOW}Wartung${RESET}" "5 Tage"
)
echo -e "\n${BOLD}Serverstatus-Übersicht:${RESET}\n"
print_table header[@] data[@]Die Hervorhebung von Unterschieden zwischen Texten kann bei der Analyse von Konfigurationsdateien oder Log-Dateien hilfreich sein:
#!/bin/bash
# Farbdefinitionen
RESET="\033[0m"
RED="\033[31m"
GREEN="\033[32m"
BLUE="\033[34m"
# Funktion zum Vergleich und zur Hervorhebung von Unterschieden
highlight_diff() {
local file1="$1"
local file2="$2"
if [ ! -f "$file1" ] || [ ! -f "$file2" ]; then
echo "Eine oder beide Dateien existieren nicht."
return 1
fi
echo -e "${BLUE}Vergleiche Dateien:${RESET}"
echo -e " ${BLUE}< ${file1}${RESET}"
echo -e " ${BLUE}> ${file2}${RESET}"
echo
# Zeilen vergleichen und Unterschiede hervorheben
diff -u "$file1" "$file2" | sed -n '1,2d; s/^-/'"${RED}-/; s/^+/""${GREEN}+/; s/$/""${RESET}/; p"
echo
echo -e "${BLUE}Legende:${RESET}"
echo -e "${RED}- Zeile in ${file1} entfernt${RESET}"
echo -e "${GREEN}+ Zeile in ${file2} hinzugefügt${RESET}"
}
# Beispielverwendung
highlight_diff "config.orig" "config.new"Da nicht alle Terminals Farben oder erweiterte Steuerungen unterstützen, ist es wichtig, Fallback-Mechanismen zu implementieren:
#!/bin/bash
# Farbunterstützung prüfen und konfigurieren
setup_colors() {
# Prüfe, ob das Terminal Farben unterstützt
if [ -t 1 ] && [ -n "$TERM" ] && [ "$TERM" != "dumb" ]; then
# Prüfe die Anzahl der unterstützten Farben
ncolors=$(tput colors 2>/dev/null || echo 0)
if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then
# Mindestens 8 Farben verfügbar
RESET=$(tput sgr0)
BOLD=$(tput bold)
BLACK=$(tput setaf 0)
RED=$(tput setaf 1)
GREEN=$(tput setaf 2)
YELLOW=$(tput setaf 3)
BLUE=$(tput setaf 4)
MAGENTA=$(tput setaf 5)
CYAN=$(tput setaf 6)
WHITE=$(tput setaf 7)
HAS_COLORS=1
else
# Keine oder zu wenige Farben, deaktiviere Farben
RESET=""
BOLD=""
BLACK=""
RED=""
GREEN=""
YELLOW=""
BLUE=""
MAGENTA=""
CYAN=""
WHITE=""
HAS_COLORS=0
fi
else
# Nicht-interaktives Terminal oder "dumb"-Terminal, deaktiviere Farben
RESET=""
BOLD=""
BLACK=""
RED=""
GREEN=""
YELLOW=""
BLUE=""
MAGENTA=""
CYAN=""
WHITE=""
HAS_COLORS=0
fi
# Exportiere die Farbvariablen für untergeordnete Skripte
export RESET BOLD BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE HAS_COLORS
}
# Terminalbreite ermitteln oder Standardwert verwenden
get_terminal_width() {
if [ -t 1 ] && command -v tput >/dev/null 2>&1; then
tput cols 2>/dev/null || echo 80
else
echo 80
fi
}
# Generische Funktion für Statusnachrichten, die mit oder ohne Farben funktioniert
print_status() {
local type=$1
local message=$2
local width=$(get_terminal_width)
local max_message_length=$((width - 10))
# Kürze zu lange Nachrichten
if [ ${#message} -gt $max_message_length ]; then
message="${message:0:$((max_message_length-3))}..."
fi
if [ "$HAS_COLORS" -eq 1 ]; then
case "$type" in
info) echo -e "${BLUE}[INFO]${RESET} $message" ;;
success) echo -e "${GREEN}[ OK ]${RESET} $message" ;;
warning) echo -e "${YELLOW}[WARN]${RESET} $message" ;;
error) echo -e "${RED}[FAIL]${RESET} $message" ;;
*) echo " $message" ;;
esac
else
case "$type" in
info) echo "[INFO] $message" ;;
success) echo "[ OK ] $message" ;;
warning) echo "[WARN] $message" ;;
error) echo "[FAIL] $message" ;;
*) echo " $message" ;;
esac
fi
}
# Initialisiere Farben
setup_colors
# Beispielverwendung
print_status info "Skript gestartet"
print_status warning "Festplattenplatz wird knapp"
print_status error "Datei nicht gefunden"
print_status success "Backup erfolgreich abgeschlossen"
# Demo mit Balken-Anzeige
echo
echo "Beispiel für eine Fortschrittsanzeige mit Fallback:"
width=$(get_terminal_width)
bar_width=$((width - 10))
for i in $(seq 1 $bar_width); do
percent=$((i * 100 / bar_width))
# Berechne die Anzahl der gefüllten und leeren Zeichen
filled=$((i * bar_width / bar_width))
empty=$((bar_width - filled))
# Erstelle den Fortschrittsbalken
if [ "$HAS_COLORS" -eq 1 ]; then
bar=""
for ((j=0; j<filled; j++)); do
bar="${bar}█"
done
for ((j=0; j<empty; j++)); do
bar="${bar}░"
done
printf "\r${YELLOW}[${GREEN}${bar}${YELLOW}] ${BLUE}%3d%%${RESET}" $percent
else
bar=""
for ((j=0; j<filled; j++)); do
bar="${bar}#"
done
for ((j=0; j<empty; j++)); do
bar="${bar}."
done
printf "\r[%s] %3d%%" "$bar" $percent
fi
if [ $i -lt $bar_width ]; then
sleep 0.05
fi
done
echoWährend die bisher behandelten Techniken zur Terminal-Steuerung und
Farbausgabe direkt in Shell-Skripten implementiert werden können, bieten
spezialisierte TUI-Werkzeuge (Text User Interface) vorgefertigte
Lösungen für komplexere Benutzeroberflächen. Diese Tools ermöglichen die
Erstellung anspruchsvoller interaktiver Anwendungen, ohne dass Sie alle
Einzelheiten der Terminalsteuerung selbst implementieren müssen. In
diesem Abschnitt werden wir die wichtigsten TUI-Werkzeuge untersuchen,
mit einem besonderen Fokus auf dialog, eines der am
weitesten verbreiteten Tools dieser Art.
dialog ist ein Kommandozeilenprogramm, das vorgefertigte
Dialog-Boxen für Shell-Skripte bereitstellt. Es ist in den meisten
Linux-Distributionen verfügbar und kann leicht installiert werden:
# Debian/Ubuntu
sudo apt-get install dialog
# Red Hat/CentOS/Fedora
sudo dnf install dialog
# Arch Linux
sudo pacman -S dialog
# macOS mit Homebrew
brew install dialogDie Grundsyntax von dialog ist:
dialog --typebox [Optionen] <Höhe> <Breite> [<Optionale Parameter>]#!/bin/bash
# Einfaches Nachrichtenfenster
dialog --title "Information" --msgbox "Die Operation wurde erfolgreich abgeschlossen." 8 50
# Nachrichtenfenster mit Timeout (10 Sekunden)
dialog --title "Hinweis" --timeout 10 --msgbox "Dieses Fenster schließt sich in 10 Sekunden." 8 50#!/bin/bash
# Ja/Nein-Abfrage
dialog --title "Bestätigung" --yesno "Möchten Sie fortfahren?" 7 40
response=$?
case $response in
0) echo "Benutzer hat Ja gewählt." ;;
1) echo "Benutzer hat Nein gewählt." ;;
255) echo "Dialog wurde abgebrochen." ;;
esac#!/bin/bash
# Texteingabe
dialog --title "Benutzereingabe" --inputbox "Bitte geben Sie Ihren Namen ein:" 8 40 2> /tmp/input.txt
name=$(cat /tmp/input.txt)
echo "Eingegebener Name: $name"
# Passwort-Eingabe (verdeckt)
dialog --title "Passwort" --passwordbox "Bitte geben Sie Ihr Passwort ein:" 8 40 2> /tmp/password.txt
password=$(cat /tmp/password.txt)
echo "Passwort wurde eingegeben."#!/bin/bash
# Einfaches Menü
dialog --title "Hauptmenü" --menu "Bitte wählen Sie eine Option:" 15 50 5 \
1 "Systeminfo anzeigen" \
2 "Festplattenbelegung prüfen" \
3 "Benutzer verwalten" \
4 "Netzwerkeinstellungen" \
5 "Beenden" 2> /tmp/menu_choice.txt
choice=$(cat /tmp/menu_choice.txt)
case $choice in
1) echo "Systeminfo ausgewählt" ;;
2) echo "Festplattenbelegung ausgewählt" ;;
3) echo "Benutzerverwaltung ausgewählt" ;;
4) echo "Netzwerkeinstellungen ausgewählt" ;;
5) echo "Beenden ausgewählt" ;;
*) echo "Keine Auswahl getroffen" ;;
esac#!/bin/bash
# Checkbox-Liste für Mehrfachauswahl
dialog --title "Komponenten auswählen" --checklist "Wählen Sie zu installierende Komponenten:" 15 60 5 \
"webserver" "Webserver (Apache)" ON \
"database" "Datenbank (MySQL)" OFF \
"php" "PHP" ON \
"mail" "Mail-Server" OFF \
"firewall" "Firewall-Konfiguration" ON 2> /tmp/checklist_result.txt
selected=$(cat /tmp/checklist_result.txt)
echo "Ausgewählte Komponenten: $selected"#!/bin/bash
# Radiobox-Liste für Einzelauswahl
dialog --title "Betriebssystem wählen" --radiolist "Wählen Sie ein Betriebssystem:" 15 50 5 \
"linux" "Linux" ON \
"windows" "Windows" OFF \
"macos" "macOS" OFF \
"freebsd" "FreeBSD" OFF \
"other" "Andere" OFF 2> /tmp/radio_result.txt
os=$(cat /tmp/radio_result.txt)
echo "Ausgewähltes Betriebssystem: $os"#!/bin/bash
# Einfacher Fortschrittsbalken
(
for i in $(seq 1 100); do
echo $i
sleep 0.05
done
) | dialog --title "Installation" --gauge "Installiere Pakete..." 8 50 0
# Fortschrittsbalken mit Textaktualisierung
(
echo "10"; echo "XXX"; echo "Herunterladen..."; echo "XXX"; sleep 1
echo "30"; echo "XXX"; echo "Entpacken..."; echo "XXX"; sleep 1
echo "50"; echo "XXX"; echo "Installieren..."; echo "XXX"; sleep 1
echo "70"; echo "XXX"; echo "Konfigurieren..."; echo "XXX"; sleep 1
echo "90"; echo "XXX"; echo "Aufräumen..."; echo "XXX"; sleep 1
echo "100"; echo "XXX"; echo "Abgeschlossen!"; echo "XXX"; sleep 0.5
) | dialog --title "Installation" --gauge "Starte Installation..." 8 50 0#!/bin/bash
# Temporäre Dateien für Dialog-Ausgaben
temp_dir=$(mktemp -d)
CHOICE_FILE="$temp_dir/choice"
VALUES_FILE="$temp_dir/values"
# Aufräumen bei Beendigung
trap 'rm -rf "$temp_dir"' EXIT
# Willkommensbildschirm
dialog --title "Installationsassistent" --msgbox "Willkommen zum Installationsassistent.\n\nDieser Assistent führt Sie durch die Installation und Konfiguration der Anwendung." 10 60
# Schritt 1: Zielverzeichnis
dialog --title "Schritt 1: Installationsort" --inputbox "Bitte geben Sie das Installationsverzeichnis ein:" 8 60 "/opt/myapp" 2> "$VALUES_FILE"
INSTALL_DIR=$(cat "$VALUES_FILE")
# Schritt 2: Komponentenauswahl
dialog --title "Schritt 2: Komponenten" --checklist "Wählen Sie zu installierende Komponenten:" 15 60 5 \
"core" "Kernanwendung (erforderlich)" ON \
"docs" "Dokumentation" ON \
"examples" "Beispiele" ON \
"devel" "Entwicklertools" OFF \
"extras" "Zusätzliche Funktionen" OFF 2> "$VALUES_FILE"
COMPONENTS=$(cat "$VALUES_FILE")
# Schritt 3: Konfigurationsoptionen
dialog --title "Schritt 3: Konfiguration" --menu "Wählen Sie eine Konfigurationsvorlage:" 15 60 3 \
"minimal" "Minimale Installation" \
"standard" "Standardinstallation" \
"full" "Vollständige Installation" 2> "$VALUES_FILE"
CONFIG_TEMPLATE=$(cat "$VALUES_FILE")
# Schritt 4: Netzwerkeinstellungen
dialog --title "Schritt 4: Netzwerk" --form "Netzwerkeinstellungen:" 15 60 4 \
"Port:" 1 1 "8080" 1 20 10 0 \
"Hostname:" 2 1 "localhost" 2 20 20 0 \
"Max. Verbindungen:" 3 1 "100" 3 20 5 0 \
"Timeout (s):" 4 1 "30" 4 20 5 0 2> "$VALUES_FILE"
NETWORK_SETTINGS=$(cat "$VALUES_FILE")
# Werte in separate Variablen aufteilen
PORT=$(echo "$NETWORK_SETTINGS" | sed -n '1p')
HOSTNAME=$(echo "$NETWORK_SETTINGS" | sed -n '2p')
MAX_CONN=$(echo "$NETWORK_SETTINGS" | sed -n '3p')
TIMEOUT=$(echo "$NETWORK_SETTINGS" | sed -n '4p')
# Zusammenfassung anzeigen
dialog --title "Zusammenfassung" --msgbox "Installationsübersicht:
- Installationsverzeichnis: $INSTALL_DIR
- Komponenten: $COMPONENTS
- Konfiguration: $CONFIG_TEMPLATE
- Netzwerkeinstellungen:
- Port: $PORT
- Hostname: $HOSTNAME
- Max. Verbindungen: $MAX_CONN
- Timeout: $TIMEOUT Sekunden" 15 70
# Bestätigung vor der Installation
dialog --title "Installation starten" --yesno "Sind Sie bereit, die Installation zu starten?" 7 50
response=$?
if [ $response -eq 0 ]; then
# Simulation einer Installation mit Fortschrittsbalken
(
echo "10"; echo "XXX"; echo "Vorbereitung der Installation..."; echo "XXX"; sleep 1
echo "20"; echo "XXX"; echo "Erstelle Verzeichnisse..."; echo "XXX"; sleep 1
echo "30"; echo "XXX"; echo "Extrahiere Dateien..."; echo "XXX"; sleep 2
echo "50"; echo "XXX"; echo "Installiere Komponenten: $COMPONENTS"; echo "XXX"; sleep 2
echo "70"; echo "XXX"; echo "Konfiguriere Anwendung..."; echo "XXX"; sleep 1
echo "90"; echo "XXX"; echo "Führe abschließende Einrichtung durch..."; echo "XXX"; sleep 1
echo "100"; echo "XXX"; echo "Installation abgeschlossen!"; echo "XXX"; sleep 0.5
) | dialog --title "Installation läuft" --gauge "Starte Installation..." 8 60 0
# Abschlussbildschirm
dialog --title "Installation abgeschlossen" --msgbox "Die Installation wurde erfolgreich abgeschlossen!\n\nSie können die Anwendung jetzt starten mit:\n$INSTALL_DIR/bin/myapp" 10 60
else
dialog --title "Installation abgebrochen" --msgbox "Die Installation wurde abgebrochen." 7 40
fi#!/bin/bash
# Temporäre Dateien
temp_dir=$(mktemp -d)
CHOICE_FILE="$temp_dir/choice"
# Aufräumen bei Beendigung
trap 'rm -rf "$temp_dir"' EXIT
# Hauptmenü-Funktion
show_main_menu() {
dialog --clear --title "Systemverwaltung" --menu "Wählen Sie eine Option:" 15 60 8 \
1 "Systeminfo anzeigen" \
2 "Festplattenbelegung prüfen" \
3 "Prozesse verwalten" \
4 "Netzwerkstatus" \
5 "Benutzer verwalten" \
6 "Dienste verwalten" \
7 "Systemprotokollierung" \
0 "Beenden" 2> "$CHOICE_FILE"
# Abbruch durch ESC oder Cancel
if [ $? -ne 0 ]; then
return 1
fi
choice=$(cat "$CHOICE_FILE")
return 0
}
# Systeminfo anzeigen
show_system_info() {
# Sammle Systeminformationen
system_info=$(
echo "Hostname: $(hostname)"
echo "Kernel: $(uname -r)"
echo "Betriebssystem: $(grep PRETTY_NAME /etc/os-release | cut -d= -f2 | tr -d '"')"
echo "Uptime: $(uptime -p)"
echo "CPU: $(grep 'model name' /proc/cpuinfo | head -1 | cut -d: -f2 | sed 's/^[ \t]*//')"
echo "Speicher: $(free -h | grep Mem | awk '{print $3 " von " $2 " verwendet"}')"
echo "Swap: $(free -h | grep Swap | awk '{print $3 " von " $2 " verwendet"}')"
)
dialog --title "Systeminformationen" --msgbox "$system_info" 15 70
}
# Festplattenbelegung anzeigen
show_disk_usage() {
# Sammle Festplatteninformationen
disk_info=$(df -h | grep -v "tmpfs" | grep -v "udev")
dialog --title "Festplattenbelegung" --msgbox "$disk_info" 20 70
}
# Prozessverwaltung
manage_processes() {
# Liste der Top-Prozesse nach CPU-Nutzung
processes=$(ps aux --sort=-%cpu | head -11)
dialog --title "Prozessverwaltung" --menu "Top-Prozesse nach CPU-Nutzung:" 20 70 10 \
"refresh" "Liste aktualisieren" \
"kill" "Prozess beenden (PID eingeben)" 2> "$CHOICE_FILE"
if [ $? -ne 0 ]; then
return
fi
action=$(cat "$CHOICE_FILE")
case $action in
refresh)
manage_processes
;;
kill)
dialog --title "Prozess beenden" --inputbox "Geben Sie die PID des zu beendenden Prozesses ein:" 8 50 2> "$CHOICE_FILE"
if [ $? -eq 0 ]; then
pid=$(cat "$CHOICE_FILE")
if [ -n "$pid" ] && [ "$pid" -eq "$pid" ] 2>/dev/null; then
kill -15 $pid 2>/dev/null
dialog --title "Prozessverwaltung" --msgbox "Signal an Prozess $pid gesendet." 6 50
else
dialog --title "Fehler" --msgbox "Ungültige PID: $pid" 6 50
fi
fi
manage_processes
;;
esac
}
# Netzwerkstatus anzeigen
show_network_status() {
# Sammle Netzwerkinformationen
network_info=$(
echo "=== Netzwerkschnittstellen ==="
ip -br addr show
echo
echo "=== Offene Ports (tcp) ==="
netstat -tuln | grep tcp
echo
echo "=== Offene Ports (udp) ==="
netstat -tuln | grep udp
echo
echo "=== Aktive Verbindungen ==="
netstat -tn | head -20
)
dialog --title "Netzwerkstatus" --msgbox "$network_info" 20 70
}
# Benutzerverwaltung
manage_users() {
# Liste der Benutzer
users=$(awk -F: '$3 >= 1000 && $3 < 65534 {print $1}' /etc/passwd | sort)
# Erstelle Menüoptionen
options=""
for user in $users; do
options="$options $user \"Benutzer $user verwalten\""
done
options="$options create \"Neuen Benutzer erstellen\""
# Benutzermenü anzeigen
eval "dialog --title \"Benutzerverwaltung\" --menu \"Wählen Sie einen Benutzer:\" 15 60 8 $options 2> \"$CHOICE_FILE\""
if [ $? -ne 0 ]; then
return
fi
selection=$(cat "$CHOICE_FILE")
if [ "$selection" = "create" ]; then
dialog --title "Neuer Benutzer" --inputbox "Benutzername:" 8 40 2> "$CHOICE_FILE"
if [ $? -eq 0 ]; then
new_user=$(cat "$CHOICE_FILE")
dialog --title "Bestätigung" --yesno "Benutzer $new_user erstellen?" 6 40
if [ $? -eq 0 ]; then
dialog --title "In Arbeit" --msgbox "Benutzer $new_user würde jetzt erstellt werden.\n(Diese Funktion ist in diesem Demo-Skript nicht implementiert)" 8 50
fi
fi
else
dialog --title "Benutzerverwaltung" --menu "Optionen für Benutzer $selection:" 12 60 4 \
1 "Passwort ändern" \
2 "Gruppen anzeigen" \
3 "Benutzer löschen" \
4 "Zurück" 2> "$CHOICE_FILE"
if [ $? -eq 0 ]; then
user_action=$(cat "$CHOICE_FILE")
case $user_action in
1)
dialog --title "In Arbeit" --msgbox "Passwort für $selection würde jetzt geändert werden.\n(Diese Funktion ist in diesem Demo-Skript nicht implementiert)" 8 50
;;
2)
user_groups=$(groups $selection 2>/dev/null || echo "Fehler: Benutzergruppen konnten nicht abgerufen werden")
dialog --title "Gruppen für $selection" --msgbox "$user_groups" 8 60
;;
3)
dialog --title "Bestätigung" --yesno "Benutzer $selection wirklich löschen?" 6 50
if [ $? -eq 0 ]; then
dialog --title "In Arbeit" --msgbox "Benutzer $selection würde jetzt gelöscht werden.\n(Diese Funktion ist in diesem Demo-Skript nicht implementiert)" 8 50
fi
;;
esac
fi
fi
manage_users
}
# Dienstverwaltung
manage_services() {
# Liste der Systemd-Dienste
services=$(systemctl list-units --type=service --all | grep ".service" | awk '{print $1}' | head -15)
# Erstelle Menüoptionen
options=""
for service in $services; do
status=$(systemctl is-active $service 2>/dev/null)
if [ "$status" = "active" ]; then
status="aktiv"
else
status="inaktiv"
fi
options="$options $service \"$service ($status)\""
done
# Dienstmenü anzeigen
eval "dialog --title \"Dienstverwaltung\" --menu \"Wählen Sie einen Dienst:\" 20 70 15 $options 2> \"$CHOICE_FILE\""
if [ $? -ne 0 ]; then
return
fi
service=$(cat "$CHOICE_FILE")
status=$(systemctl is-active $service 2>/dev/null)
if [ "$status" = "active" ]; then
action="stop"
action_text="stoppen"
else
action="start"
action_text="starten"
fi
dialog --title "Dienstverwaltung" --menu "Optionen für $service:" 12 60 4 \
1 "$service $action_text" \
2 "$service neustarten" \
3 "Status von $service anzeigen" \
4 "Zurück" 2> "$CHOICE_FILE"
if [ $? -eq 0 ]; then
service_action=$(cat "$CHOICE_FILE")
case $service_action in
1)
dialog --title "In Arbeit" --msgbox "$service würde jetzt $action_text.\n(Diese Funktion ist in diesem Demo-Skript nicht implementiert)" 8 50
;;
2)
dialog --title "In Arbeit" --msgbox "$service würde jetzt neugestartet werden.\n(Diese Funktion ist in diesem Demo-Skript nicht implementiert)" 8 50
;;
3)
service_status=$(systemctl status $service 2>&1 || echo "Fehler: Status konnte nicht abgerufen werden")
dialog --title "Status von $service" --msgbox "$service_status" 20 70
;;
esac
fi
manage_services
}
# Systemprotokollierung
show_logs() {
log_files="/var/log/syslog /var/log/auth.log /var/log/kern.log /var/log/dmesg"
# Erstelle Menüoptionen
options=""
for log in $log_files; do
if [ -f "$log" ]; then
log_name=$(basename $log)
options="$options $log \"$log_name anzeigen\""
fi
done
# Logmenü anzeigen
eval "dialog --title \"Systemprotokolle\" --menu \"Wählen Sie eine Protokolldatei:\" 15 60 6 $options 2> \"$CHOICE_FILE\""
if [ $? -ne 0 ]; then
return
fi
selected_log=$(cat "$CHOICE_FILE")
if [ -f "$selected_log" ]; then
log_content=$(tail -n 500 $selected_log 2>&1 || echo "Fehler: Protokoll konnte nicht gelesen werden")
dialog --title "Inhalt von $selected_log" --textbox /dev/stdin 20 80 <<< "$log_content"
else
dialog --title "Fehler" --msgbox "Protokolldatei nicht gefunden: $selected_log" 6 50
fi
show_logs
}
# Hauptprogrammschleife
while true; do
show_main_menu
if [ $? -ne 0 ]; then
break
fi
choice=$(cat "$CHOICE_FILE")
case $choice in
1) show_system_info ;;
2) show_disk_usage ;;
3) manage_processes ;;
4) show_network_status ;;
5) manage_users ;;
6) manage_services ;;
7) show_logs ;;
0) break ;;
esac
done
# Aufräumen und beenden
clear
echo "Programm beendet."dialog bietet verschiedene Möglichkeiten zur Anpassung
des Erscheinungsbilds:
#!/bin/bash
# Farben und Stil anpassen
export DIALOGRC="/path/to/custom/dialogrc"
# Oder temporäre Anpassungen vornehmen
dialog --colors \
--title "\Z1\Zr\ZbRotes Titel mit Rahmen\Zn" \
--backtitle "\Z2Grüner Hintergrundtitel\Zn" \
--clear \
--ok-label "Weiter" \
--cancel-label "Abbrechen" \
--msgbox "\Z3Blaue Nachricht\Zn\n\Z4Türkiser Text\Zn\n\Z5Magenta\Zn\n\Z6Cyan\Zn\n\Z7Weiß\Zn" 15 50Eine benutzerdefinierte dialogrc-Datei könnte so
aussehen:
# Dialog-Konfigurationsdatei
# Schattenstil
use_shadow = ON
# Grenzen verwenden
use_colors = ON
# Farbschema
screen_color = (BLUE,BLACK,ON)
shadow_color = (BLACK,BLACK,ON)
dialog_color = (WHITE,BLACK,OFF)
title_color = (YELLOW,BLACK,ON)
border_color = (WHITE,BLACK,ON)
button_active_color = (WHITE,BLUE,ON)
button_inactive_color = (WHITE,BLACK,OFF)
button_key_active_color = (WHITE,BLUE,ON)
button_key_inactive_color = (RED,BLACK,OFF)
button_label_active_color = (YELLOW,BLUE,ON)
button_label_inactive_color = (BLACK,BLACK,ON)
inputbox_color = (BLACK,WHITE,OFF)
inputbox_border_color = (BLACK,WHITE,OFF)
searchbox_color = (BLACK,WHITE,OFF)
searchbox_title_color = (BLUE,WHITE,ON)
searchbox_border_color = (WHITE,WHITE,ON)
position_indicator_color = (YELLOW,BLACK,ON)
menubox_color = (BLACK,BLACK,OFF)
menubox_border_color = (WHITE,BLACK,ON)
item_color = (WHITE,BLACK,OFF)
item_selected_color = (WHITE,BLUE,ON)
tag_color = (YELLOW,BLACK,ON)
tag_selected_color = (YELLOW,BLUE,ON)
tag_key_color = (RED,BLACK,OFF)
tag_key_selected_color = (RED,BLUE,ON)
check_color = (BLACK,WHITE,OFF)
check_selected_color = (WHITE,BLUE,ON)
uarrow_color = (GREEN,BLACK,ON)
darrow_color = (GREEN,BLACK,ON)
Neben dialog gibt es mehrere andere TUI-Werkzeuge, die
für verschiedene Anwendungsfälle geeignet sein können:
whiptail ist eine leichtere Alternative zu
dialog und ist auf vielen Systemen standardmäßig
installiert:
#!/bin/bash
# Einfaches Menü mit whiptail
whiptail --title "Hauptmenü" --menu "Wählen Sie eine Option:" 15 60 5 \
"1" "Option 1" \
"2" "Option 2" \
"3" "Option 3" 3>&1 1>&2 2>&3YAD bietet fortschrittlichere GTK-basierte Dialoge für Skripte:
#!/bin/bash
# Einfaches Formular mit YAD
yad --title "Benutzerdaten" --form \
--field="Name" "" \
--field="E-Mail" "" \
--field="Geburtsdatum:DT" "" \
--field="Geschlecht:CB" "Männlich!Weiblich!Divers" \
--button="Abbrechen:1" --button="OK:0"Zenity ist ein GNOME-Tool für einfache GTK-Dialoge:
#!/bin/bash
# Einfacher Dateiauswahldialog mit Zenity
file=$(zenity --file-selection --title="Wählen Sie eine Datei")
if [ $? -eq 0 ]; then
zenity --info --text="Sie haben $file ausgewählt."
else
zenity --error --text="Keine Datei ausgewählt."
fiFür komplexere TUI-Anwendungen können Sie direkt mit Programmierbibliotheken wie Ncurses arbeiten:
#!/usr/bin/env python3
import curses
def main(stdscr):
# Initialisiere Farben
curses.start_color()
curses.init_pair(1, curses.COLOR_BLUE, curses.COLOR_BLACK)
curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK)
# Setze den Bildschirm auf
stdscr.clear()
stdscr.refresh()
# Erstelle ein Fenster
height, width = stdscr.getmaxyx()
win = curses.newwin(height-4, width-4, 2, 2)
win.box()
win.addstr(2, 2, "Willkommen bei Curses!", curses.color_pair(1))
win.addstr(4, 2, "Drücken Sie eine Taste zum Beenden.", curses.color_pair(2))
win.refresh()
# Warte auf Tastendruck
win.getch()
# Starte das Hauptprogramm
curses.wrapper(main)Temporäre Dateien verwenden: Nutzen Sie
mktemp zum Erstellen temporärer Dateien und stellen Sie
sicher, dass diese beim Beenden des Skripts aufgeräumt werden.
Fehlerbehandlung implementieren: Überprüfen Sie stets die Rückgabewerte von Dialog-Aufrufen, um Abbrüche durch den Benutzer zu erkennen.
Benutzerfreundlichkeit: Bieten Sie klare Anweisungen, konsistente Navigationsmöglichkeiten und brechen Sie komplexe Aufgaben in überschaubare Schritte auf.
Bildschirmgröße berücksichtigen: Passen Sie die Größe Ihrer Dialoge an, um auch auf kleineren Terminals gut auszusehen.
Tastaturkürzel anbieten: Implementieren Sie Tastaturkürzel für häufig verwendete Funktionen.
Internationalisierung: Wenn Ihr Skript in verschiedenen Sprachen verwendet werden soll, nutzen Sie Übersetzungsdateien.