In Unix/Linux-Systemen sind Signale eine Form der
Interprozesskommunikation. Sie werden verwendet, um Prozessen
mitzuteilen, dass bestimmte Ereignisse eingetreten sind. Ein Signal kann
von einem Prozess an einen anderen gesendet werden, vom Kernel an einen
Prozess oder vom Benutzer durch Tastenkombinationen wie
Ctrl+C (SIGINT) oder Ctrl+Z (SIGTSTP).
Wenn ein Skript oder ein Programm ein Signal empfängt, hat es
standardmäßig eine vordefinierte Reaktion. Zum Beispiel führt das
SIGTERM-Signal normalerweise zur Beendigung des Prozesses. Mit dem
trap-Kommando können wir jedoch definieren, wie unser
Shell-Skript auf verschiedene Signale reagieren soll.
Das kill-Kommando ist der zentrale Mechanismus zum
Senden von Signalen an Prozesse unter Unix/Linux. Entgegen seinem Namen
dient kill nicht nur zum Beenden von Prozessen, sondern
kann jedes beliebige Signal an einen Prozess senden. Die Grundsyntax
lautet:
kill [-Signalname oder -Signalnummer] PIDBeispiele:
# Sendet SIGTERM (15) an Prozess 1234
kill 1234
# Sendet SIGKILL (9) an Prozess 1234
kill -9 1234
# oder
kill -KILL 1234
# Sendet SIGHUP (1) an Prozess 1234
kill -HUP 1234Das kill-Kommando bietet verschiedene Optionen:
kill -l: Listet alle verfügbaren Signale aufkill -0 PID: Testet, ob der Prozess existiert, ohne ein
Signal zu sendenkillall Prozessname: Sendet ein Signal an alle Prozesse
mit einem bestimmten NamenEine Übersicht aller verfügbaren Signale erhalten Sie mit:
$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAXDie am häufigsten verwendeten Signale beim Prozessmanagement sind:
Ctrl+C ausgelöst. Signalisiert eine Unterbrechung.kill. Bittet den Prozess höflich, sich zu beenden.Das trap-Kommando ermöglicht es, bestimmte Aktionen
auszuführen, wenn ein Signal empfangen wird. Die Syntax ist wie
folgt:
trap 'Befehle' SignaleDabei sind Befehle die Shell-Befehle, die ausgeführt
werden sollen, wenn eines der angegebenen Signale empfangen
wird.
Hier sind einige der am häufigsten verwendeten Signale und ihre Bedeutung:
| Signal | Nummer | Bedeutung | Standardaktion |
|---|---|---|---|
| SIGHUP | 1 | Hangup, Terminal wurde geschlossen | Beenden |
| SIGINT | 2 | Interrupt (Ctrl+C) | Beenden |
| SIGQUIT | 3 | Quit (Ctrl+\) | Beenden mit Core |
| SIGKILL | 9 | Kill | Sofort beenden |
| SIGTERM | 15 | Terminate | Beenden |
| SIGTSTP | 18 | Stop (Ctrl+Z) | Anhalten |
Hinweis: SIGKILL (9) und SIGSTOP (19) können nicht mit
trapabgefangen werden.
Ein häufiger Anwendungsfall für trap ist das Aufräumen
temporärer Dateien, wenn ein Skript beendet wird, sei es normal oder
durch ein Signal:
#!/bin/bash
# Temporäre Datei erstellen
TEMP_FILE=$(mktemp /tmp/example.XXXXXX)
# trap-Befehl, um die temporäre Datei zu löschen wenn das Skript beendet wird
trap 'echo "Räume auf..."; rm -f "$TEMP_FILE"; echo "Temporäre Datei gelöscht."' EXIT
# Etwas in die temporäre Datei schreiben
echo "Dies ist ein Test" > "$TEMP_FILE"
# Inhalt anzeigen
echo "Inhalt der temporären Datei:"
cat "$TEMP_FILE"
echo "Skript wird beendet..."In diesem Beispiel wird die temporäre Datei automatisch gelöscht,
wenn das Skript auf natürliche Weise endet oder mit exit
beendet wird. Das EXIT-Signal ist ein spezielles Pseudo-Signal, das
ausgelöst wird, wenn das Skript beendet wird, unabhängig davon, wie es
beendet wird.
Sie können mehrere Signale mit derselben Trap-Aktion versehen:
#!/bin/bash
# Die gleiche Aktion für mehrere Signale definieren
trap 'echo "Abbruch durch Signal erkannt. Beende..."' SIGINT SIGTERM
echo "Skript gestartet. PID: $$"
echo "Drücke Ctrl+C, um zu testen."
# Endlosschleife
while true; do
sleep 1
doneUm eine Trap zurückzusetzen, sodass das Signal seine Standardaktion
ausführt, verwenden Sie das Schlüsselwort -:
trap - SIGINTUm zu sehen, welche Trap-Befehle aktuell definiert sind, führen Sie
trap ohne Argumente aus:
trapHier ist ein Beispiel für ein Skript, das eine “graceful shutdown” implementiert:
#!/bin/bash
# Funktion für das Aufräumen
cleanup() {
echo "Aufräumen..."
# Hier Aufräumaktionen einfügen, z.B. temporäre Dateien löschen
rm -f /tmp/myapp_*.tmp
echo "Aufräumen abgeschlossen."
}
# Funktion für die Beendigung
terminate() {
echo "Signal zum Beenden empfangen."
cleanup
echo "Skript wird beendet."
exit 0
}
# Trap für verschiedene Beendigungssignale
trap terminate SIGINT SIGTERM
# Trap für EXIT (wird aufgerufen, wenn das Skript normal beendet wird)
trap cleanup EXIT
echo "Skript gestartet. PID: $$"
echo "Zum Beenden Ctrl+C drücken oder 'kill $$' von einem anderen Terminal aus senden."
# Simulierte Arbeit
count=0
while true; do
echo "Arbeit läuft... (Zähler: $count)"
sleep 2
((count++))
doneDie DEBUG-Trap wird vor jeder Befehlsausführung ausgelöst und ist nützlich für Debugging:
#!/bin/bash
trap 'echo "DEBUG: Gerade ausgeführter Befehl: $BASH_COMMAND"' DEBUG
echo "Dies ist ein Test."
a=5
b=10
c=$((a + b))
echo "Summe: $c"Die ERR-Trap wird ausgelöst, wenn ein Befehl mit einem Nicht-Null-Status fehlschlägt:
#!/bin/bash
trap 'echo "FEHLER: Der Befehl \"$BASH_COMMAND\" schlug fehl mit Exitcode $?"' ERR
# Diesen Befehl versuchen
cat nicht_existierende_datei.txt
# Weiterführen des Skripts
echo "Das Skript läuft trotz des Fehlers weiter."In diesem Beispiel starten wir mehrere Hintergrundprozesse und verwenden trap, um sicherzustellen, dass alle beendet werden, wenn das Hauptskript beendet wird:
#!/bin/bash
# Array für Prozess-IDs
pids=()
# Funktion zur Beendigung aller Kindprozesse
cleanup() {
echo "Beende alle Kindprozesse..."
for pid in "${pids[@]}"; do
if kill -0 "$pid" 2>/dev/null; then
echo "Beende Prozess $pid"
kill "$pid"
fi
done
wait
echo "Alle Prozesse beendet."
}
# Trap für Aufräumarbeiten
trap cleanup EXIT SIGINT SIGTERM
# Starte einige Hintergrundprozesse
for i in {1..3}; do
(
echo "Prozess $i gestartet (PID: $$)"
while true; do
echo "Prozess $i noch aktiv"
sleep 5
done
) &
# Speichere PID des Hintergrundprozesses
pids+=($!)
echo "Hintergrundprozess $i gestartet mit PID ${pids[-1]}"
done
echo "Alle Hintergrundprozesse gestartet. Hauptskript-PID: $$"
echo "Drücke Ctrl+C zum Beenden"
# Warte auf Benutzerinteraktion
while true; do
sleep 1
doneEs ist wichtig zu beachten, dass einige Signale nicht mit
trap abgefangen werden können:
Weiterhin funktioniert trap nur innerhalb der Shell, in
der es definiert wurde. Wenn ein Skript andere Programme oder
Shell-Skripte aufruft, müssen diese ihre eigenen trap-Befehle haben.
Immer aufräumen: Verwenden Sie trap mit EXIT, um sicherzustellen, dass temporäre Ressourcen freigegeben werden, unabhängig davon, wie das Skript beendet wird.
Vorsicht mit komplexen Aktionen: Die in einer trap definierten Aktionen sollten robust sein und keine Fehler verursachen, da dies zu unerwünschten Ergebnissen führen kann.
Debuggen von Trap-Aktionen: Fügen Sie Logging-Anweisungen in Ihre trap-Aktionen ein, um zu verfolgen, wann und wie sie ausgelöst werden.
Ressourcenverwaltung: Verwenden Sie trap zum Verwalten von Ressourcen wie temporären Dateien, Hintergrundprozessen und Sperrdateien.
Graceful Shutdown implementieren: Erlauben Sie Ihren Skripten, laufende Operationen ordnungsgemäß abzuschließen, bevor sie beendet werden.
Das Zusammenspiel von kill und trap ist
entscheidend für robuste Shell-Skripte:
#!/bin/bash
# PID in einer Datei speichern, damit andere Prozesse Signale senden können
echo $ > /tmp/mein_skript.pid
# Trap für verschiedene Signale
trap 'echo "SIGHUP empfangen, Konfiguration wird neu geladen..."; source config.sh' SIGHUP
trap 'echo "SIGTERM empfangen, beende ordnungsgemäß..."; cleanup; exit 0' SIGTERM
trap 'echo "SIGINT empfangen, beende..."; exit 1' SIGINT
cleanup() {
echo "Aufräumen..."
rm -f /tmp/mein_skript.pid
}
# Hauptfunktion
main() {
echo "Skript läuft mit PID $"
echo "Sende Signale mit:"
echo " kill -HUP $(cat /tmp/mein_skript.pid) # Konfiguration neu laden"
echo " kill -TERM $(cat /tmp/mein_skript.pid) # Ordnungsgemäß beenden"
echo " kill -INT $(cat /tmp/mein_skript.pid) # Sofort beenden"
while true; do
echo "Arbeit wird ausgeführt..."
sleep 10
done
}
mainMit diesem Muster können Sie: 1. Ein laufendes Skript von außen steuern 2. Unterschiedliche Reaktionen auf verschiedene Signale definieren 3. Prozesse ordnungsgemäß herunterfahren
Ein typisches Anwendungsbeispiel ist ein Daemon-Skript, das auf SIGHUP reagiert, um seine Konfiguration neu zu laden, ohne neu gestartet werden zu müssen.
#!/bin/bash
# Konfiguration
SERVICE_NAME="mein_dienst"
PID_FILE="/var/run/$SERVICE_NAME.pid"
LOG_FILE="/var/log/$SERVICE_NAME.log"
# Hilfsfunktionen
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
echo "$1"
}
start_service() {
if [ -f "$PID_FILE" ] && kill -0 $(cat "$PID_FILE") 2>/dev/null; then
log "Dienst läuft bereits mit PID $(cat "$PID_FILE")"
return 1
fi
# Dienst starten
log "Starte Dienst..."
# Hier würde der eigentliche Dienst gestartet werden
(
# trap innerhalb des Dienstes definieren
trap 'log "Signal empfangen, beende..."; exit 0' SIGINT SIGTERM
trap 'log "SIGHUP empfangen, lade Konfiguration neu..."' SIGHUP
# PID speichern
echo $ > "$PID_FILE"
log "Dienst gestartet mit PID $"
# Hauptschleife des Dienstes
while true; do
# Simulierte Arbeit
sleep 5
done
) &
# Kurz warten und prüfen, ob der Dienst noch läuft
sleep 1
if [ -f "$PID_FILE" ] && kill -0 $(cat "$PID_FILE") 2>/dev/null; then
log "Dienst erfolgreich gestartet mit PID $(cat "$PID_FILE")"
return 0
else
log "Fehler beim Starten des Dienstes"
return 1
fi
}
stop_service() {
if [ ! -f "$PID_FILE" ]; then
log "PID-Datei nicht gefunden. Dienst läuft nicht."
return 0
fi
PID=$(cat "$PID_FILE")
if ! kill -0 "$PID" 2>/dev/null; then
log "Prozess $PID existiert nicht. Entferne veraltete PID-Datei."
rm -f "$PID_FILE"
return 0
fi
log "Sende SIGTERM an Prozess $PID..."
kill -TERM "$PID"
# Warten, bis der Prozess beendet ist
for i in {1..10}; do
if ! kill -0 "$PID" 2>/dev/null; then
break
fi
log "Warte auf Beendigung des Prozesses..."
sleep 1
done
# Falls der Prozess nicht reagiert, SIGKILL senden
if kill -0 "$PID" 2>/dev/null; then
log "Prozess reagiert nicht. Sende SIGKILL..."
kill -KILL "$PID"
fi
rm -f "$PID_FILE"
log "Dienst gestoppt."
return 0
}
reload_service() {
if [ ! -f "$PID_FILE" ]; then
log "PID-Datei nicht gefunden. Dienst läuft nicht."
return 1
fi
PID=$(cat "$PID_FILE")
if ! kill -0 "$PID" 2>/dev/null; then
log "Prozess $PID existiert nicht. Entferne veraltete PID-Datei."
rm -f "$PID_FILE"
return 1
fi
log "Sende SIGHUP an Prozess $PID, um Konfiguration neu zu laden..."
kill -HUP "$PID"
return 0
}
# Hauptprogramm
case "$1" in
start)
start_service
;;
stop)
stop_service
;;
restart)
stop_service
start_service
;;
reload)
reload_service
;;
status)
if [ -f "$PID_FILE" ] && kill -0 $(cat "$PID_FILE") 2>/dev/null; then
log "Dienst läuft mit PID $(cat "$PID_FILE")"
else
log "Dienst läuft nicht"
fi
;;
*)
echo "Verwendung: $0 {start|stop|restart|reload|status}"
exit 1
;;
esac
exit 0Dieses Beispiel demonstriert ein vollständiges
Dienst-Management-Skript, das kill und trap
zusammen verwendet, um einen Daemon-Prozess zu steuern, zu überwachen
und zu verwalten.