In Unix/Linux-Systemen ist ein Prozess eine Instanz eines laufenden Programms. Jeder Prozess besteht aus Code, Daten, Ressourcen und einem Ausführungszustand. Das Verständnis des Prozessmodells ist für effektives Shell-Scripting unerlässlich, da es die Grundlage für Programmausführung, Automatisierung und Systemverwaltung bildet.
Jeder Prozess in einem Unix/Linux-System wird durch eine eindeutige Prozess-ID (PID) identifiziert. Diese numerische ID dient als primärer Bezeichner für den Prozess und wird für die meisten prozessbezogenen Operationen verwendet.
# Anzeige der PID des aktuellen Shell-Prozesses
echo $$
# Anzeige der PID des zuletzt gestarteten Hintergrundprozesses
echo $!Unix/Linux-Systeme organisieren Prozesse in einer hierarchischen Baumstruktur. Jeder Prozess hat einen Elternprozess, der ihn erzeugt hat (mit Ausnahme des init-Prozesses, der die PID 1 hat und vom Kernel beim Systemstart gestartet wird).
# Anzeige des Prozessbaums
pstree
# Alternative mit ps
ps -ef --forestWenn ein Prozess einen neuen Prozess startet, wird dieser als Kindprozess bezeichnet. Der startende Prozess wird zum Elternprozess. Diese Beziehung ist wichtig für das Verständnis der Prozesssteuerung und des Lebenszyklus von Prozessen.
Ein Prozess kann sich in verschiedenen Zuständen befinden:
# Anzeige von Prozesszuständen
ps aux | head -n 10Der zweite Buchstabe in der STAT-Spalte gibt den Prozesszustand an.
Das wichtigste Werkzeug zur Anzeige von Prozessinformationen ist der
ps-Befehl:
# Grundlegende Prozessinformationen für alle Prozesse
ps -ef
# Detaillierte Informationen mit Ressourcennutzung
ps aux
# Prozesse eines bestimmten Benutzers anzeigen
ps -u username
# Prozesse nach CPU-Nutzung sortieren
ps aux --sort=-%cpuAlternativ bietet top oder htop eine
dynamische Echtzeit-Ansicht der laufenden Prozesse:
# Dynamische Prozessüberwachung
top
# Benutzerfreundlichere Alternative (möglicherweise Installation erforderlich)
htopJeder Prozess verfügt über verschiedene Attribute:
# Offene Dateien eines Prozesses anzeigen
lsof -p PID
# Umgebungsvariablen eines Prozesses anzeigen
cat /proc/PID/environ | tr '\0' '\n'
# Arbeitsverzeichnis eines Prozesses anzeigen
readlink /proc/PID/cwdIn Shell-Skripten können Prozesse auf verschiedene Arten erzeugt werden:
Direkte Ausführung: Befehle in der Shell starten automatisch neue Prozesse
ls -la # Startet einen neuen Prozess für lsHintergrundprozesse: Mit dem
&-Operator kann ein Prozess im Hintergrund gestartet
werden
long_running_command & # Startet den Befehl im HintergrundProzesssubstitution: Ermöglicht die Verwendung der Ausgabe eines Prozesses als Datei
diff <(ls dir1) <(ls dir2) # Vergleicht die Ausgaben zweier ls-BefehleDie Umgebung eines Prozesses umfasst Variablen, die sein Verhalten beeinflussen können:
# Alle Umgebungsvariablen anzeigen
env
# Eine spezifische Umgebungsvariable für einen Prozess setzen
VARIABLE=Wert commandWenn ein Prozess einen Kindprozess erzeugt, erbt dieser normalerweise die Umgebungsvariablen des Elternprozesses.
Unix/Linux-Systeme erlauben die Begrenzung der Ressourcen, die ein Prozess verwenden kann:
# Aktuelle Ressourcenlimits anzeigen
ulimit -a
# Maximale Anzahl offener Dateien für den aktuellen Prozess setzen
ulimit -n 2048Diese Limits können in Shell-Skripten gesetzt werden, um sicherzustellen, dass Prozesse nicht zu viele Systemressourcen verbrauchen.
Die Fähigkeit, Prozesse im Hintergrund auszuführen und zu überwachen, ist ein zentraler Aspekt effektiver Shell-Skripte, besonders wenn es um langwierige Operationen geht. Hintergrundprozesse ermöglichen es, mehrere Aufgaben parallel auszuführen, ohne auf den Abschluss jeder einzelnen warten zu müssen.
Der einfachste Weg, einen Prozess im Hintergrund zu starten, ist die
Verwendung des &-Operators am Ende eines Befehls:
# Starten eines Prozesses im Hintergrund
sleep 300 &
# Die Shell gibt die PID des Hintergrundprozesses zurück
[1] 12345Nach dem Starten eines Hintergrundprozesses zeigt die Shell die Job-Nummer (in eckigen Klammern) und die PID an. Die Shell kehrt sofort zur Eingabeaufforderung zurück, während der Prozess im Hintergrund weiterläuft.
Es können mehrere Prozesse gleichzeitig im Hintergrund ausgeführt werden:
# Mehrere Hintergrundprozesse starten
long_task1 &
long_task2 &
long_task3 &Jeder dieser Prozesse erhält eine eigene Job-Nummer und PID.
Standardmäßig schreiben Hintergrundprozesse ihre Ausgabe weiterhin auf das Terminal, was störend sein kann. Um dies zu vermeiden, können Ausgaben umgeleitet werden:
# Standardausgabe und Standardfehlerausgabe in eine Datei umleiten
long_task > output.log 2>&1 &
# Ausgaben verwerfen
long_task > /dev/null 2>&1 &
# Ausgaben in unterschiedliche Dateien umleiten
long_task > output.log 2> error.log &Der jobs-Befehl zeigt alle Hintergrundprozesse an, die
von der aktuellen Shell gestartet wurden:
# Alle Hintergrundprozesse anzeigen
jobs
# Ausgabe mit PIDs
jobs -l
# Nur laufende Jobs anzeigen
jobs -r
# Nur gestoppte Jobs anzeigen
jobs -sDie Ausgabe von jobs enthält die Job-Nummer, den Status
(Running, Stopped, etc.) und den Befehl.
Der ps-Befehl kann verwendet werden, um detailliertere
Informationen über Prozesse zu erhalten:
# Alle eigenen Prozesse anzeigen
ps
# Prozess mit bestimmter PID anzeigen
ps -p 12345
# Alle Prozesse (auch die anderer Benutzer) anzeigen
ps aux
# Bestimmte Informationen filtern
ps -eo pid,ppid,cmd,%cpu,%mem --sort=-%cpuFür eine Echtzeit-Überwachung von Prozessen sind top und
htop besser geeignet:
# Standardüberwachung mit top
top
# Überwachung eines bestimmten Prozesses
top -p 12345
# Benutzerfreundlichere Alternative (möglicherweise Installation erforderlich)
htopProzesse können zwischen Vordergrund und Hintergrund verschoben werden:
# Einen Hintergrundprozess in den Vordergrund holen
fg %1 # % gefolgt von der Job-Nummer
# Einen gestoppten Prozess im Hintergrund fortsetzen
bg %2Ein laufender Vordergrundprozess kann mit Ctrl+Z
angehalten und dann mit bg im Hintergrund fortgesetzt
werden:
# Beispiel-Workflow
sleep 300 # Startet einen Prozess im Vordergrund
# [Drücke Ctrl+Z] -> Stoppt den Prozess
[1]+ Stopped sleep 300
bg # Setzt den gestoppten Prozess im Hintergrund fortProzesse können mit dem kill-Befehl beendet werden:
# Prozess mit PID beenden
kill 12345
# Prozess mit Job-Nummer beenden
kill %1
# Verschiedene Signale verwenden
kill -TERM 12345 # Normales Beenden (gleichwertig mit kill 12345)
kill -KILL 12345 # Sofortiges Beenden, kann nicht abgefangen werden
kill -HUP 12345 # Sendet das Hangup-Signal, oft für Neukonfiguration verwendetUm einen Prozess zu beenden, der nicht reagiert, kann das SIGKILL-Signal (-9) verwendet werden. Dies sollte jedoch als letzte Möglichkeit betrachtet werden:
kill -9 12345 # Erzwingt die BeendigungIn Shell-Skripten kann der wait-Befehl verwendet werden,
um zu warten, bis Hintergrundprozesse abgeschlossen sind:
#!/bin/bash
# Starten mehrerer Hintergrundprozesse
process1 &
PID1=$!
process2 &
PID2=$!
process3 &
PID3=$!
# Auf Abschluss eines bestimmten Prozesses warten
wait $PID1
echo "Prozess 1 abgeschlossen"
# Auf Abschluss aller Hintergrundprozesse warten
wait
echo "Alle Prozesse abgeschlossen"Manchmal möchte man einen Prozess nur für eine begrenzte Zeit laufen lassen:
#!/bin/bash
# Prozess im Hintergrund starten
long_process &
PID=$!
# Timeout setzen (z.B. 60 Sekunden)
timeout=60
count=0
while kill -0 $PID 2>/dev/null; do
if [ $count -ge $timeout ]; then
echo "Zeitüberschreitung - Prozess wird beendet"
kill -9 $PID
break
fi
sleep 1
((count++))
doneDer kill -0-Befehl prüft nur, ob der Prozess existiert,
ohne ein Signal zu senden.
Um den Exitstatus eines Hintergrundprozesses zu erfassen:
#!/bin/bash
# Prozess im Hintergrund starten
some_command &
PID=$!
# Auf Abschluss warten
wait $PID
EXIT_STATUS=$?
if [ $EXIT_STATUS -eq 0 ]; then
echo "Prozess erfolgreich abgeschlossen"
else
echo "Prozess fehlgeschlagen mit Exitstatus $EXIT_STATUS"
fiUm sicherzustellen, dass Hintergrundprozesse weiterlaufen, selbst wenn die Shell geschlossen wird:
# Prozess starten, der das SIGHUP-Signal ignoriert
nohup long_process &
# Alternativ: Bestehenden Hintergrundprozess von der Shell "lösen"
long_process &
disown %1Die Priorität von Prozessen kann mit nice und
renice gesteuert werden:
# Prozess mit niedrigerer Priorität starten (höherer Nice-Wert)
nice -n 10 resource_intensive_command &
# Priorität eines laufenden Prozesses ändern
renice +10 -p 12345Nice-Werte reichen von -20 (höchste Priorität, nur von root nutzbar) bis 19 (niedrigste Priorität).
Für die Ausführung vieler paralleler Prozesse ist xargs
mit der -P-Option nützlich:
# 4 parallele Instanzen ausführen
cat task_list.txt | xargs -P 4 -I {} ./process_task.sh {}Wenn ein Kindprozess beendet wird, aber der Elternprozess seinen Status nicht abfragt, bleibt er als “Zombie” im System:
# Zombie-Prozesse identifizieren
ps aux | grep 'Z'In gut geschriebenen Shell-Skripten sollten Zombie-Prozesse durch korrektes Warten auf Kindprozesse vermieden werden:
#!/bin/bash
# Korrekte Behandlung von Kindprozessen
child_process &
wait $! # Verhindert Zombies
# Alternativ: Signal-Handler für SIGCHLD
trap 'wait $!' SIGCHLDSignale sind ein grundlegender Mechanismus zur Interprozess-Kommunikation in Unix/Linux-Systemen. Sie ermöglichen es, Prozessen Ereignisse oder Anfragen zu übermitteln und darauf zu reagieren. Im Shell-Scripting spielen Signale eine wichtige Rolle bei der Prozesssteuerung, Fehlerbehandlung und der Implementierung ordnungsgemäßer Aufräumroutinen.
Ein Signal ist im Wesentlichen eine Software-Unterbrechung, die an einen Prozess gesendet wird, um ein bestimmtes Ereignis anzuzeigen. Wenn ein Prozess ein Signal empfängt, unterbricht das Betriebssystem dessen normale Ausführung und führt eine vorbestimmte Aktion aus, die vom Signaltyp abhängt.
Hier sind die am häufigsten verwendeten Signale:
| Signal | Nummer | Beschreibung | Standardaktion |
|---|---|---|---|
| SIGHUP | 1 | Hangup (Terminal geschlossen) | Beenden |
| SIGINT | 2 | Interrupt (meist Ctrl+C) | Beenden |
| SIGQUIT | 3 | Quit (meist Ctrl+\) | Beenden mit Core-Dump |
| SIGKILL | 9 | Kill (nicht abfangbar) | Beenden (erzwungen) |
| SIGTERM | 15 | Terminate (normal beenden) | Beenden |
| SIGSTOP | 19 | Stop (nicht abfangbar) | Anhalten |
| SIGTSTP | 20 | Terminal stop (meist Ctrl+Z) | Anhalten |
| SIGCONT | 18 | Continue (nach Stop) | Fortsetzen |
| SIGUSR1 | 10 | Benutzerdefiniert 1 | Beenden |
| SIGUSR2 | 12 | Benutzerdefiniert 2 | Beenden |
# Liste aller verfügbaren Signale anzeigen
kill -lDer kill-Befehl ist das Hauptwerkzeug zum Senden von
Signalen an Prozesse:
# SIGTERM (Standard) an einen Prozess senden
kill 1234
# Bestimmtes Signal an einen Prozess senden
kill -SIGINT 1234 # Oder: kill -2 1234
# Signal an alle Prozesse einer Prozessgruppe senden
kill -SIGTERM -1234 # Negative PID bezieht sich auf die Prozessgruppe
# Signal an alle Prozesse eines Benutzers senden
pkill -SIGTERM -u username
# Signal an Prozesse senden, die einem Muster entsprechen
pkill -SIGTERM firefoxMit dem trap-Befehl können Shell-Skripte auf Signale
reagieren und eigene Handler definieren:
#!/bin/bash
# Funktion zum Aufräumen bei Beendigung
cleanup() {
echo "Aufräumen und Beenden..."
rm -f /tmp/tempfile_$$
exit 0
}
# Signal-Handler einrichten
trap cleanup SIGINT SIGTERM EXIT
# Temporäre Datei erstellen
touch /tmp/tempfile_$$
# Langwierige Operation simulieren
echo "Prozess läuft. PID: $$"
echo "Drücke Ctrl+C um zu beenden."
while true; do
sleep 1
doneIn diesem Beispiel: - Die cleanup-Funktion wird
ausgeführt, wenn das Skript SIGINT (Ctrl+C), SIGTERM oder beim normalen
Beenden (EXIT) empfängt - Das Skript erstellt eine temporäre Datei und
stellt sicher, dass diese aufgeräumt wird, unabhängig davon, wie das
Skript beendet wird - Die Variable $$ enthält die PID des
aktuellen Shell-Prozesses
trap [Aktion] [Signalliste]Die Aktion kann sein: - Ein Befehl oder eine Funktion - Ein leerer
String '' (Signal ignorieren) - -
(Standardaktion wiederherstellen)
# Signal ignorieren
trap '' SIGINT
# Standardaktion wiederherstellen
trap - SIGINT
# Mehrere Befehle als Handler
trap 'echo "Abbruch!"; rm -f /tmp/tempfile_$$; exit 1' SIGINT#!/bin/bash
TEMPFILES=()
TEMPFILES+=("$(mktemp)")
TEMPFILES+=("$(mktemp)")
cleanup() {
echo "Temporäre Dateien entfernen..."
for file in "${TEMPFILES[@]}"; do
rm -f "$file"
done
}
trap cleanup EXIT#!/bin/bash
PID_FILE="/var/run/mydaemon.pid"
shutdown() {
echo "Graceful Shutdown wird durchgeführt..."
# Kindprozesse beenden
pkill -P $$
# PID-Datei entfernen
rm -f "$PID_FILE"
exit 0
}
# PID speichern
echo $$ > "$PID_FILE"
# Signal-Handler einrichten
trap shutdown SIGINT SIGTERM
# Daemon-Loop
while true; do
# Hauptlogik hier
sleep 10
done#!/bin/bash
timeout_handler() {
echo "Zeitlimit überschritten!"
kill -9 $CHILD_PID 2>/dev/null
exit 1
}
# SIGALRM-Handler einrichten
trap timeout_handler SIGALRM
# Timeout in Sekunden
TIMEOUT=30
# Alarm setzen
(sleep $TIMEOUT && kill -SIGALRM $$) &
ALARM_PID=$!
# Langwierige Operation starten
long_running_command &
CHILD_PID=$!
# Auf Abschluss warten
wait $CHILD_PID
RESULT=$?
# Alarm-Prozess beenden
kill $ALARM_PID 2>/dev/null
exit $RESULTSIGKILL (9) und SIGSTOP (19) können nicht abgefangen, blockiert oder ignoriert werden. Sie werden immer vom Betriebssystem durchgesetzt:
# Diese Signal-Handler werden nie ausgeführt
trap 'echo "Dies wird nie angezeigt"' SIGKILL SIGSTOPSignal-Handler werden nicht automatisch an Subshells vererbt:
#!/bin/bash
trap 'echo "Hauptskript fängt Signal ab"' SIGINT
# Dieser Subshell-Prozess erbt den Handler nicht
(
# Eigenen Handler definieren
trap 'echo "Subshell fängt Signal ab"; exit 1' SIGINT
echo "Subshell läuft. Drücke Ctrl+C."
sleep 30
)
# Warten auf Abschluss der Subshell
waitBefehle, die über eine Pipeline verbunden sind, sind Teil derselben Prozessgruppe. Signale werden standardmäßig an die gesamte Prozessgruppe gesendet:
#!/bin/bash
# Handler für das Hauptskript
trap 'echo "Hauptskript fängt Signal ab"' SIGINT
# Pipeline starten (alle Prozesse in einer Prozessgruppe)
cat /dev/urandom | head -c 100000 | sort | uniq | wc -l &
# PID der Prozessgruppe erhalten
pgid=$!
echo "Prozessgruppe läuft mit PGID: $pgid"
echo "Drücke Ctrl+C oder warte 10 Sekunden."
sleep 10
# Alternative: Signal nur an einen bestimmten Prozess in der Gruppe senden
# kill -SIGINT $(pgrep -P $pgid | head -1)Benutzerdefinierte Signale (SIGUSR1, SIGUSR2) eignen sich gut für die Kommunikation zwischen Skripten:
#!/bin/bash
# Prozess A: Signal-Empfänger
receiver() {
echo "Empfänger gestartet. PID: $$"
handle_usr1() {
echo "Konfiguration neu laden..."
}
handle_usr2() {
echo "Statusbericht generieren..."
}
trap handle_usr1 SIGUSR1
trap handle_usr2 SIGUSR2
echo "Warte auf Signale. Beende mit Ctrl+C."
while true; do
sleep 1
done
}
# Prozess B: Signal-Sender
sender() {
echo "Sender gestartet."
echo "1) SIGUSR1 senden"
echo "2) SIGUSR2 senden"
echo "q) Beenden"
while true; do
read -p "Befehl: " cmd
case $cmd in
1) kill -SIGUSR1 $RECEIVER_PID ;;
2) kill -SIGUSR2 $RECEIVER_PID ;;
q) exit 0 ;;
*) echo "Unbekannter Befehl" ;;
esac
done
}
# Empfänger im Hintergrund starten
receiver &
RECEIVER_PID=$!
# Sender im Vordergrund starten
sender
# Aufräumen
kill $RECEIVER_PIDSignale ermöglichen asynchrone Ereignisbehandlung in Shell-Skripten:
#!/bin/bash
# Globaler Status
STATUS="Inaktiv"
# Signal-Handler
handle_event() {
case $1 in
start)
STATUS="Aktiv"
echo "Status: $STATUS"
;;
stop)
STATUS="Inaktiv"
echo "Status: $STATUS"
;;
status)
echo "Status: $STATUS"
;;
esac
}
# Signal-Handler zuweisen
trap 'handle_event start' SIGUSR1
trap 'handle_event stop' SIGUSR2
trap 'handle_event status' SIGHUP
echo "Event-Handler gestartet. PID: $$"
echo "Sende Signale mit:"
echo " kill -SIGUSR1 $$ # Start"
echo " kill -SIGUSR2 $$ # Stop"
echo " kill -SIGHUP $$ # Status"
# Hauptloop
while true; do
sleep 1
doneImmer EXIT abfangen: Um eine konsistente Aufräumung bei allen Beendigungsarten zu gewährleisten
trap cleanup EXITIdempotente Handler: Signal-Handler sollten mehrfach ausführbar sein ohne Schaden anzurichten
cleanup() {
# Variable verhindern mehrfache Ausführung
if [ "$CLEANUP_DONE" = "1" ]; then
return
fi
CLEANUP_DONE=1
# Aufräumen hier
rm -f "$TEMP_FILE"
}Minimale Handler: Signal-Handler sollten kurz und zuverlässig sein
# Gut: Nur Flags setzen im Handler
trap 'TERMINATE=1' SIGTERM
while [ "$TERMINATE" != "1" ]; do
# Hauptlogik hier
sleep 1
done
# Aufräumen nach der SchleifeSignalblockierung in kritischen Abschnitten:
# Signale temporär blockieren
trap '' SIGINT SIGTERM
# Kritischer Abschnitt hier
critical_operation
# Standardverhalten wiederherstellen
trap - SIGINT SIGTERMDie Job-Kontrolle ist ein leistungsstarkes Feature moderner Shells, das es Benutzern ermöglicht, mehrere Prozesse effizient zu verwalten, zwischen Vordergrund- und Hintergrundausführung zu wechseln und die Ausführung von Prozessen zu pausieren oder fortzusetzen. Diese Funktionen sind besonders nützlich für Shell-Skripte, die komplexe Aufgaben mit mehreren parallel laufenden Komponenten ausführen müssen.
In der Shell-Terminologie ist ein “Job” eine Gruppe von Prozessen, die von einem einzelnen Befehl oder einer Pipeline gestartet wurden. Die Shell weist jedem Job eine Jobnummer zu, die für die Steuerung dieses Jobs verwendet werden kann.
Jobs können in zwei Modi ausgeführt werden:
# Vordergrundausführung
sleep 60
# Hintergrundausführung
sleep 60 &Der jobs-Befehl listet alle aktiven Jobs auf, die von
der aktuellen Shell-Sitzung verwaltet werden:
# Alle Jobs anzeigen
jobs
# Ausgabeformat:
# [1] + Running sleep 100 &
# [2] - Stopped nano testfile.txt
# Mit zusätzlichen Optionen
jobs -l # Mit Process-IDs anzeigen
jobs -p # Nur Process-IDs anzeigen
jobs -s # Nur angehaltene Jobs anzeigen
jobs -r # Nur laufende Jobs anzeigenIn der Ausgabe: - Die Zahl in eckigen Klammern ist die Job-Nummer - Das Plus-Zeichen (+) kennzeichnet den aktuellen Job (wird standardmäßig von den Befehlen fg und bg verwendet) - Das Minus-Zeichen (-) kennzeichnet den Job, der zum aktuellen Job wird, wenn der aktuelle Job beendet wird - Der Status zeigt an, ob der Job läuft, angehalten oder beendet ist
Der fg-Befehl bringt einen Hintergrund- oder
angehaltenen Job in den Vordergrund:
# Aktuellen Job in den Vordergrund bringen
fg
# Bestimmten Job in den Vordergrund bringen
fg %1 # Job mit Nummer 1
fg %vim # Job, dessen Befehlsname mit "vim" beginnt
fg %% # Aktueller Job (äquivalent zu fg)
fg %- # Vorheriger JobDer bg-Befehl setzt einen angehaltenen Job im
Hintergrund fort:
# Aktuellen angehaltenen Job im Hintergrund fortsetzen
bg
# Bestimmten angehaltenen Job im Hintergrund fortsetzen
bg %2Die Tastenkombination Ctrl+Z sendet das SIGTSTP-Signal
an den aktuellen Vordergrundjob, wodurch dieser angehalten wird:
sleep 100 # Starte einen Vordergrundjob
# [Drücke Ctrl+Z]
# [1]+ Stopped sleep 100
bg # Setze den angehaltenen Job im Hintergrund fortDie Tastenkombination Ctrl+C sendet das SIGINT-Signal an
den aktuellen Vordergrundjob, wodurch dieser in der Regel beendet
wird:
sleep 100 # Starte einen Vordergrundjob
# [Drücke Ctrl+C]
# ^CJobs können auf verschiedene Arten referenziert werden:
%n - Job mit der Nummer n%% oder %+ - Aktueller Job%- - Vorheriger Job%string - Job, dessen Befehlsname mit “string”
beginnt%?string - Job, dessen Befehlszeile “string”
enthält# Beispiele für Jobreferenzen
kill %1 # Sendet SIGTERM an Job 1
bg %?find # Setzt Job fort, der "find" in der Befehlszeile enthält
fg %+ # Bringt den aktuellen Job in den VordergrundIn Shell-Skripten ist die Job-Kontrolle standardmäßig deaktiviert.
Sie kann mit dem Befehl set -m oder
set -o monitor aktiviert werden:
#!/bin/bash
# Job-Kontrolle aktivieren
set -m
# Jobs starten
process1 &
process2 &
# Jobs auflisten
jobs
# Auf einen bestimmten Job warten
fg %1Ein nützliches Muster für die Wiederherstellung angehaltener Jobs:
#!/bin/bash
set -m
# Funktion zur Wiederherstellung angehaltener Jobs
resume_stopped_jobs() {
local stopped_jobs
stopped_jobs=$(jobs -sp)
if [ -n "$stopped_jobs" ]; then
echo "Wiederherstellung angehaltener Jobs..."
while read -r pid; do
kill -CONT "$pid" 2>/dev/null
done <<< "$stopped_jobs"
fi
}
# Signal-Handler einrichten
trap resume_stopped_jobs EXIT
# Hauptlogik des Skripts
# ...Für komplexere Anwendungen kann es nützlich sein, Jobs in Gruppen zu organisieren:
#!/bin/bash
set -m
# Neue Prozessgruppe starten
start_job_group() {
# Subshell mit eigener Prozessgruppe
(
# Mehrere Prozesse starten
process1 &
process2 &
process3 &
# Auf alle warten
wait
) &
# PID der Prozessgruppe (= PID der Subshell)
echo $!
}
# Job-Gruppe starten
group_pid=$(start_job_group)
echo "Job-Gruppe gestartet mit PID: $group_pid"
# Später: Ganze Gruppe beenden
kill -TERM -"$group_pid" # Negative PID bezieht sich auf die ProzessgruppeBash unterstützt Co-Prozesse, die eine bidirektionale Kommunikation mit einem Hintergrundjob ermöglichen:
#!/bin/bash
# Co-Prozess starten
coproc SORTER {
sort
}
# Daten an den Co-Prozess senden
echo -e "banana\napple\ncherry" >&${SORTER[1]}
# Eingabekanal schließen
exec {SORTER[1]}>&-
# Daten vom Co-Prozess lesen
sorted_output=$(cat <&${SORTER[0]})
echo "Sortierte Liste:"
echo "$sorted_output"
# Ausgabekanal schließen
exec {SORTER[0]}>&-
# Auf Beendigung des Co-Prozesses warten
wait $SORTER_PIDFür komplexere Anwendungen, die mehrere Jobs verwalten müssen:
#!/bin/bash
set -m
# Assoziatives Array für Job-Tracking
declare -A jobs_map
# Funktion zum Starten eines Hintergrundjobs
start_job() {
local job_name=$1
local command=$2
$command &
local pid=$!
jobs_map["$job_name"]=$pid
echo "Job '$job_name' gestartet mit PID $pid"
}
# Funktion zum Beenden eines Jobs
stop_job() {
local job_name=$1
local pid=${jobs_map["$job_name"]}
if [ -n "$pid" ] && kill -0 $pid 2>/dev/null; then
echo "Beende Job '$job_name' (PID $pid)..."
kill -TERM $pid
wait $pid 2>/dev/null
echo "Job '$job_name' beendet."
else
echo "Job '$job_name' existiert nicht oder läuft nicht."
fi
}
# Funktion zum Auflisten aller Jobs
list_jobs() {
echo "Aktive Jobs:"
for job_name in "${!jobs_map[@]}"; do
local pid=${jobs_map["$job_name"]}
if kill -0 $pid 2>/dev/null; then
echo " $job_name (PID $pid): Aktiv"
else
echo " $job_name (PID $pid): Beendet"
unset jobs_map["$job_name"]
fi
done
}
# Beispielverwendung
start_job "logger" "tail -f /var/log/syslog"
start_job "counter" "for i in {1..100}; do echo \$i; sleep 1; done"
sleep 5
list_jobs
stop_job "counter"
list_jobs
# Vor dem Beenden alle Jobs stoppen
trap 'for job in "${!jobs_map[@]}"; do stop_job "$job"; done' EXITFür Anwendungen, die Jobs basierend auf Systemauslastung oder anderen Kriterien planen müssen:
#!/bin/bash
set -m
# Maximal zulässige Anzahl gleichzeitiger Jobs
MAX_CONCURRENT_JOBS=4
# Warteschlange für ausstehende Aufgaben
task_queue=()
# Funktion zum Hinzufügen einer Aufgabe zur Warteschlange
add_task() {
task_queue+=("$1")
echo "Aufgabe zur Warteschlange hinzugefügt: $1"
schedule_tasks
}
# Funktion zum Planen und Ausführen von Aufgaben
schedule_tasks() {
# Anzahl aktiver Jobs ermitteln
local active_jobs=$(jobs -r | wc -l)
# Solange Platz für mehr Jobs und Aufgaben in der Warteschlange sind
while [ $active_jobs -lt $MAX_CONCURRENT_JOBS ] && [ ${#task_queue[@]} -gt 0 ]; do
# Nächste Aufgabe aus der Warteschlange nehmen
local next_task=${task_queue[0]}
task_queue=("${task_queue[@]:1}") # Erste Aufgabe entfernen
# Aufgabe im Hintergrund starten
echo "Starte Aufgabe: $next_task"
eval "$next_task" &
# Anzahl aktiver Jobs aktualisieren
active_jobs=$(jobs -r | wc -l)
done
}
# Signal-Handler für beendete Hintergrundjobs
handle_job_completion() {
schedule_tasks
}
trap handle_job_completion CHLD
# Beispielverwendung
for i in {1..10}; do
add_task "echo 'Aufgabe $i gestartet'; sleep $((RANDOM % 5 + 1)); echo 'Aufgabe $i beendet'"
done
# Warten, bis alle Aufgaben abgearbeitet sind
wait
echo "Alle Aufgaben abgeschlossen."Aufräumen hinterlassener Jobs:
# Vor dem Beenden alle Hintergrundjobs beenden
trap 'jobs -p | xargs -r kill' EXITÜberwachung langlebiger Jobs:
# Prozessüberwachung mit timeout
monitor_job() {
local pid=$1
local max_runtime=$2
(
sleep $max_runtime
if kill -0 $pid 2>/dev/null; then
echo "Job $pid überschreitet maximale Laufzeit"
kill -TERM $pid
fi
) &
}
long_process &
job_pid=$!
monitor_job $job_pid 3600 # 1 Stunde TimeoutJob-Status protokollieren:
# Job mit Statusprotokollierung starten
log_file="job_$(date +%Y%m%d_%H%M%S).log"
{
echo "Job gestartet: $(date)"
time my_command
echo "Job beendet: $(date) mit Status: $?"
} > "$log_file" 2>&1 &Ressourcenkontrolle für Jobs:
# Limitierte Ressourcen für einen Job
(
# CPU-Priorität reduzieren
renice +10 -p $$ > /dev/null
# I/O-Priorität reduzieren (benötigt ionice)
ionice -c 3 -p $$
# Speicherlimit setzen
ulimit -v 1000000 # 1 GB virtueller Speicher
# CPU-Zeit begrenzen
ulimit -t 3600 # 1 Stunde CPU-Zeit
# Job ausführen
intensive_process
) &In der heutigen Welt der Mehrkernprozessoren und verteilten Systeme ist die Fähigkeit, Aufgaben zu parallelisieren, zu einer grundlegenden Anforderung für effiziente Skripte geworden. Die Parallelisierung in Shell-Skripten ermöglicht es, die verfügbaren Systemressourcen besser zu nutzen und die Ausführungszeit von Aufgaben erheblich zu reduzieren. Dieser Abschnitt behandelt verschiedene Techniken zur Parallelisierung von Aufgaben in Shell-Skripten.
Die einfachste Form der Parallelisierung in Shell-Skripten ist die
Verwendung des &-Operators, um Prozesse im Hintergrund
zu starten:
#!/bin/bash
echo "Starte parallele Aufgaben..."
# Mehrere Befehle parallel ausführen
task1 &
task2 &
task3 &
# Auf Abschluss aller Hintergrundprozesse warten
wait
echo "Alle Aufgaben abgeschlossen."Diese grundlegende Technik hat jedoch einige Einschränkungen: - Keine einfache Möglichkeit, die Anzahl gleichzeitiger Prozesse zu begrenzen - Schwierigkeiten bei der Erfassung und Verarbeitung von Rückgabewerten - Keine integrierte Fehlerbehandlung
Der xargs-Befehl mit der Option -P ist ein
leistungsstarkes Werkzeug zur kontrollierten Parallelisierung:
#!/bin/bash
# Liste von Aufgaben
echo "Datei1 Datei2 Datei3 Datei4 Datei5 Datei6 Datei7 Datei8" | \
# Maximal 4 parallele Prozesse
xargs -P 4 -n 1 ./verarbeite_datei.shDer Parameter -P gibt die maximale Anzahl gleichzeitiger
Prozesse an, während -n die Anzahl der Argumente pro
Befehlsaufruf festlegt.
Ein praktisches Beispiel für die Bildkonvertierung:
#!/bin/bash
# Alle JPG-Dateien finden und in PNG konvertieren, mit maximal 8 parallelen Prozessen
find . -name "*.jpg" -type f | \
xargs -P 8 -I {} convert {} {}.pngGNU Parallel ist ein spezialisiertes Werkzeug für die
Parallelisierung, das mehr Funktionen als xargs bietet:
#!/bin/bash
# Sicherstellen, dass GNU Parallel installiert ist
if ! command -v parallel &> /dev/null; then
echo "GNU Parallel ist nicht installiert. Bitte installieren Sie es mit:"
echo " apt-get install parallel # Debian/Ubuntu"
echo " yum install parallel # CentOS/RHEL"
exit 1
fi
# Grundlegende Verwendung: Liste von Dateien verarbeiten
find . -name "*.log" | \
parallel --max-procs=8 ./analyze_log.sh {}
# Mit Jobnamen und Fortschrittsanzeige
find . -name "*.log" | \
parallel --max-procs=8 --joblog parallel.log --progress \
'./analyze_log.sh {} > {}.report'GNU Parallel bietet zahlreiche fortgeschrittene
Funktionen:
# Automatische CPU-Kern-Erkennung
parallel --jobs 100% ./task.sh ::: input1 input2 input3
# Unterschiedliche Eingabequellen kombinieren
parallel ./process.sh {1} {2} ::: A B C ::: 1 2 3
# Ausgabe in der gleichen Reihenfolge wie die Eingabe
find . -name "*.txt" | parallel --keep-order 'wc -l {}'
# Ergebnisse in einer CSV-Datei speichern
parallel --results results.csv './test.sh {}' ::: input1 input2 input3Für Umgebungen ohne xargs -P oder
GNU Parallel kann ein einfacher Semaphor implementiert
werden:
#!/bin/bash
# Maximale Anzahl gleichzeitiger Prozesse
MAX_PROCS=4
# Funktion zum Starten eines Prozesses mit Semaphor
run_with_semaphore() {
# Aktuelle Anzahl laufender Hintergrundprozesse
local running=$(jobs -r | wc -l)
# Warten, bis ein Platz frei ist
while [ $running -ge $MAX_PROCS ]; do
sleep 0.5
running=$(jobs -r | wc -l)
done
# Prozess starten
"$@" &
}
# Beispielverwendung
for file in *.txt; do
run_with_semaphore ./process_file.sh "$file"
done
# Auf Abschluss aller Hintergrundprozesse warten
waitFür eine präzisere Kontrolle kann ein Semaphor mit Named Pipes (FIFOs) implementiert werden:
#!/bin/bash
# Maximale Anzahl gleichzeitiger Prozesse
MAX_PROCS=4
FIFO="/tmp/semaphore_$$"
# Aufräumen beim Beenden
cleanup() {
rm -f "$FIFO"
}
trap cleanup EXIT
# FIFO erstellen
mkfifo "$FIFO"
# Semaphor initialisieren
for ((i=0; i<MAX_PROCS; i++)); do
echo $i > "$FIFO" &
done
# Funktion zum Ausführen eines Befehls unter Semaphorkontrolle
run_with_semaphore() {
local slot
# Slot aus dem Semaphor lesen (blockiert, wenn keiner verfügbar)
read slot < "$FIFO"
# Befehl ausführen und Slot zurückgeben, wenn fertig
( "$@"; echo $slot > "$FIFO" ) &
}
# Beispielverwendung
for file in *.dat; do
run_with_semaphore ./analyze.sh "$file"
done
# Auf Abschluss aller Prozesse warten
waitFür komplexere Anwendungen kann ein Worker-Pool-Modell implementiert werden:
#!/bin/bash
# Konfiguration
NUM_WORKERS=4
TASK_QUEUE="/tmp/task_queue_$$"
RESULT_QUEUE="/tmp/result_queue_$$"
# Aufräumen beim Beenden
cleanup() {
rm -f "$TASK_QUEUE" "$RESULT_QUEUE"
# Alle Worker-Prozesse beenden
jobs -p | xargs -r kill
}
trap cleanup EXIT
# FIFOs erstellen
mkfifo "$TASK_QUEUE" "$RESULT_QUEUE"
# Worker-Funktion
worker() {
local worker_id=$1
local task
echo "Worker $worker_id gestartet"
# Auf Aufgaben aus der Warteschlange warten
while read task; do
# Beenden, wenn Terminierungssignal empfangen
if [ "$task" = "TERMINATE" ]; then
break
fi
echo "Worker $worker_id verarbeitet Aufgabe: $task"
# Aufgabe ausführen und Ergebnis speichern
local result=$(eval "$task" 2>&1)
local status=$?
# Ergebnis zurückmelden
echo "$worker_id:$task:$status:$result" > "$RESULT_QUEUE"
done < "$TASK_QUEUE"
echo "Worker $worker_id beendet"
}
# Worker starten
for ((i=1; i<=NUM_WORKERS; i++)); do
worker $i &
done
# Funktion zum Hinzufügen einer Aufgabe zur Warteschlange
add_task() {
echo "$1" > "$TASK_QUEUE"
}
# Funktion zum Sammeln von Ergebnissen
collect_results() {
local expected=$1
local count=0
local results=()
while [ $count -lt $expected ]; do
# Auf ein Ergebnis warten
if read result < "$RESULT_QUEUE"; then
results+=("$result")
((count++))
fi
done
# Ergebnisse zurückgeben
for result in "${results[@]}"; do
echo "$result"
done
}
# Beispielverwendung: Aufgaben zur Warteschlange hinzufügen
TASKS=(
"sleep 2 && echo 'Aufgabe 1 abgeschlossen'"
"sleep 1 && echo 'Aufgabe 2 abgeschlossen'"
"sleep 3 && echo 'Aufgabe 3 abgeschlossen'"
"sleep 1 && echo 'Aufgabe 4 abgeschlossen'"
"sleep 2 && echo 'Aufgabe 5 abgeschlossen'"
"sleep 1 && echo 'Aufgabe 6 abgeschlossen'"
)
# Aufgaben senden
for task in "${TASKS[@]}"; do
add_task "$task"
done
# Ergebnisse sammeln
echo "Ergebnisse:"
collect_results ${#TASKS[@]} | sort
# Worker beenden
for ((i=1; i<=NUM_WORKERS; i++)); do
add_task "TERMINATE"
done
# Warten, bis alle Worker beendet sind
wait
echo "Alle Worker beendet"Das Fork-Join-Muster ist ein gängiges Paradigma für parallele Verarbeitung:
#!/bin/bash
# Funktion zur parallelen Verarbeitung von Teilen einer Aufgabe
fork_join() {
local num_parts=$1
local processor=$2
local input=$3
local output=$4
# Verzeichnis für temporäre Dateien
local temp_dir=$(mktemp -d)
# Aufgabe in Teile aufteilen (Fork)
split -n l/$num_parts "$input" "$temp_dir/part_"
# Teile parallel verarbeiten
ls "$temp_dir/part_"* | xargs -P "$num_parts" -I {} \
bash -c "$processor {} {}.out"
# Ergebnisse zusammenführen (Join)
cat "$temp_dir"/part_*.out > "$output"
# Aufräumen
rm -rf "$temp_dir"
}
# Beispielverwendung für Textverarbeitung
processor="grep 'ERROR' | sort -u"
fork_join 4 "$processor" "große_logdatei.log" "fehler_zusammenfassung.txt"Für rechenintensive Aufgaben können Prozesse auf mehrere Maschinen verteilt werden:
#!/bin/bash
# Liste der verfügbaren Server
SERVERS=("server1" "server2" "server3" "server4")
# Funktion zum Ausführen eines Befehls auf einem Remote-Server
run_on_server() {
local server=$1
local command=$2
echo "Führe aus auf $server: $command"
ssh "$server" "$command"
}
# Funktion zum Verteilen von Aufgaben auf Server
distribute_tasks() {
local tasks=("$@")
local num_tasks=${#tasks[@]}
local num_servers=${#SERVERS[@]}
local pids=()
# Aufgaben auf Server verteilen
for ((i=0; i<num_tasks; i++)); do
local server_idx=$((i % num_servers))
local server=${SERVERS[$server_idx]}
local task=${tasks[$i]}
# Aufgabe im Hintergrund auf Server ausführen
run_on_server "$server" "$task" &
pids+=($!)
done
# Auf Abschluss aller Aufgaben warten
for pid in "${pids[@]}"; do
wait $pid
done
}
# Beispielverwendung
TASKS=(
"find /var/log -name '*.log' -type f -mtime -1 | xargs grep 'ERROR'"
"find /var/log -name '*.log' -type f -mtime -1 | xargs grep 'WARNING'"
"find /var/log -name '*.log' -type f -mtime -1 | xargs grep 'CRITICAL'"
"find /var/log -name '*.log' -type f -mtime -1 | xargs grep 'FATAL'"
)
distribute_tasks "${TASKS[@]}"Mit GNU Parallel kann dies noch einfacher gestaltet
werden:
parallel --sshlogin server1,server2,server3,server4 ./process.sh ::: input1 input2 input3 input4Für I/O-lastige Aufgaben kann ein asynchrones Modell effizienter sein:
#!/bin/bash
# Maximale Anzahl gleichzeitiger Aufgaben
MAX_CONCURRENT=10
# Zähler für laufende Aufgaben
declare -i running=0
# Funktion für asynchrone Ausführung
async_run() {
local command=$1
# Aufgabe starten
{
eval "$command"
# Signal senden, wenn Aufgabe beendet ist
kill -USR1 $$
} &
((running++))
# Warten, wenn maximale Anzahl erreicht ist
if [ $running -ge $MAX_CONCURRENT ]; then
wait -n # Warten auf Beendigung eines beliebigen Prozesses
((running--))
fi
}
# Signal-Handler für beendete Aufgaben
handle_task_completion() {
((running > 0)) && ((running--))
}
trap handle_task_completion USR1
# Beispielverwendung mit URL-Anfragen
URLS=(
"https://example.com/api/resource1"
"https://example.com/api/resource2"
"https://example.com/api/resource3"
# ...weitere URLs...
)
for url in "${URLS[@]}"; do
async_run "curl -s '$url' > '$(basename "$url").json'"
done
# Auf Abschluss aller verbleibenden Aufgaben warten
waitFür adaptive Parallelität basierend auf verfügbaren Ressourcen:
#!/bin/bash
# Funktion zur Bestimmung der optimalen Parallelität
get_optimal_parallelism() {
# CPU-Kerne ermitteln
local cpu_cores=$(nproc)
# Verfügbaren Arbeitsspeicher in MB ermitteln
local mem_available=$(free -m | awk '/^Mem:/ {print $7}')
# Geschätzter Speicherbedarf pro Prozess in MB
local mem_per_process=200
# Maximale Prozesse basierend auf CPU und Speicher berechnen
local max_by_cpu=$cpu_cores
local max_by_mem=$((mem_available / mem_per_process))
# Minimum wählen, aber mindestens 1
local optimal=$(( max_by_cpu < max_by_mem ? max_by_cpu : max_by_mem ))
echo $((optimal > 0 ? optimal : 1))
}
# Optimale Parallelität ermitteln
PARALLEL_JOBS=$(get_optimal_parallelism)
echo "Starte $PARALLEL_JOBS parallele Jobs basierend auf Systemressourcen"
# Mit xargs verwenden
find . -name "*.csv" | xargs -P $PARALLEL_JOBS -I {} ./process_file.sh {}Ressourcenkontrolle berücksichtigen:
# Limitierte Ressourcen pro Prozess
parallel 'ulimit -v 500000; ./memory_intensive_task {}' ::: inputs/*Zuverlässiges Logging implementieren:
# Jeder parallele Prozess schreibt in eine eigene Logdatei
parallel --joblog parallel.log './task.sh {} > logs/{}.log 2>&1' ::: inputs/*Wiederaufnahme abgebrochener Jobs:
# GNU Parallel kann unterbrochene Jobs wieder aufnehmen
parallel --resume --joblog joblog.txt './task.sh {}' ::: inputs/*Kommunikation zwischen parallelen Prozessen:
Für Prozesse, die miteinander kommunizieren müssen, können Named Pipes verwendet werden:
mkfifo /tmp/pipe1 /tmp/pipe2
# Prozess 1
{
while read line < /tmp/pipe1; do
echo "Prozess 1 empfing: $line"
echo "Antwort von Prozess 1" > /tmp/pipe2
done
} &
# Prozess 2
{
echo "Nachricht von Prozess 2" > /tmp/pipe1
read response < /tmp/pipe2
echo "Prozess 2 empfing: $response"
} &
wait
rm -f /tmp/pipe1 /tmp/pipe2Fehlerbehandlung und Robustheit:
# Parallele Aufgaben mit Fehlerbehandlung
parallel --halt soon,fail=1 './task.sh {}' ::: inputs/*Die effektive Zusammenarbeit mehrerer gleichzeitig laufender Prozesse erfordert Synchronisations- und Kommunikationsmechanismen. In der Shell-Programmierung stehen verschiedene Techniken zur Verfügung, um diese Herausforderungen zu bewältigen. Dieser Abschnitt behandelt die wichtigsten Methoden, mit denen Prozesse koordiniert werden und Daten austauschen können.
Der wait-Befehl ist der einfachste Mechanismus zur
Prozesssynchronisation:
#!/bin/bash
# Prozesse im Hintergrund starten
process1 &
PID1=$!
process2 &
PID2=$!
# Auf einen bestimmten Prozess warten
wait $PID1
echo "Prozess 1 wurde abgeschlossen"
# Auf alle Hintergrundprozesse warten
wait
echo "Alle Prozesse wurden abgeschlossen"Der wait-Befehl unterstützt auch das Abfragen des
Exit-Status:
process_data &
wait $!
if [ $? -eq 0 ]; then
echo "Datenverarbeitung erfolgreich"
else
echo "Fehler bei der Datenverarbeitung"
fiMit wait -n (verfügbar in neueren Bash-Versionen) kann
auf die Beendigung eines beliebigen Child-Prozesses gewartet werden:
#!/bin/bash
# Timeout-Funktion
wait_with_timeout() {
local pid=$1
local timeout=$2
# Timeout-Prozess starten
(
sleep $timeout
kill -0 $pid 2>/dev/null && kill -TERM $pid
) &
local timeout_pid=$!
# Auf den Hauptprozess warten
wait $pid
local exit_status=$?
# Timeout-Prozess beenden
kill $timeout_pid 2>/dev/null
return $exit_status
}
# Beispielverwendung
long_running_process &
process_pid=$!
if wait_with_timeout $process_pid 60; then
echo "Prozess erfolgreich abgeschlossen"
else
echo "Prozess wurde nach Timeout beendet oder schlug fehl"
fiLockfiles sind eine einfache Methode zur Synchronisation zwischen Prozessen:
#!/bin/bash
LOCKFILE="/tmp/myprocess.lock"
# Funktion zum Erwerben des Locks
acquire_lock() {
# Atomares Erstellen der Lockdatei mit Shell-Umleitung
if (set -o noclobber; echo "$$" > "$LOCKFILE") 2>/dev/null; then
trap 'rm -f "$LOCKFILE"; exit $?' INT TERM EXIT
return 0
else
return 1
fi
}
# Funktion zum Freigeben des Locks
release_lock() {
rm -f "$LOCKFILE"
trap - INT TERM EXIT
}
# Versuchen, den Lock zu erwerben
if ! acquire_lock; then
echo "Ein anderer Prozess läuft bereits (PID: $(cat "$LOCKFILE"))"
exit 1
fi
echo "Lock erworben, kritischer Abschnitt beginnt"
# Kritischer Abschnitt
sleep 10
echo "Kritische Operation wird ausgeführt"
# Lock freigeben
release_lock
echo "Lock freigegeben, kritischer Abschnitt beendet"Das flock-Programm bietet eine zuverlässigere Methode
für Dateilocks:
#!/bin/bash
LOCKFILE="/tmp/myprocess.lock"
# Kritischen Abschnitt mit flock schützen
(
# Versuchen, einen exklusiven Lock zu erwerben, ohne zu blockieren
if ! flock -n 200; then
echo "Lock konnte nicht erworben werden. Ein anderer Prozess läuft bereits."
exit 1
fi
echo "Lock erworben, kritischer Abschnitt beginnt"
sleep 10
echo "Kritische Operation wird ausgeführt"
echo "Lock wird freigegeben"
# Lock wird automatisch beim Schließen des Filedeskriptors freigegeben
) 200>"$LOCKFILE"Der flock-Befehl kann auch verwendet werden, um
parallele Ausführungen zu begrenzen:
#!/bin/bash
# Maximal 3 gleichzeitige Instanzen erlauben
MAX_INSTANCES=3
for i in {1..10}; do
(
# Versuchen, einen Lock auf Filedeskriptor 200 zu erwerben
# -w 0: nicht warten, wenn kein Lock verfügbar
flock -w 0 200 || { echo "Instance $i: Konnte keinen Lock erhalten"; exit 1; }
echo "Instance $i: Lock erworben, Verarbeitung beginnt"
sleep $((RANDOM % 5 + 1))
echo "Instance $i: Verarbeitung abgeschlossen"
) 200>/tmp/limited_instances.lock.$((i % MAX_INSTANCES)) &
done
# Auf Abschluss aller Hintergrundprozesse warten
waitPipes sind der grundlegendste Mechanismus zur Kommunikation zwischen Prozessen:
#!/bin/bash
# Standard-Pipeline
ls -la | grep "^d" | sort -k 5 -n
# Pipeline in einer Variablen speichern
directories=$(ls -la | grep "^d" | sort -k 5 -n)
echo "Gefundene Verzeichnisse: $directories"
# Process Substitution für bidirektionale Kommunikation
diff <(ls -la dir1) <(ls -la dir2)Ein praktisches Beispiel für die Verwendung von Pipes in Skripten:
#!/bin/bash
# Producer-Consumer-Muster mit Pipes
producer() {
for i in {1..10}; do
echo "Daten $i"
sleep 1
done
}
consumer() {
while read line; do
echo "Verarbeite: $line"
# Datenverarbeitung hier
done
}
# Producer und Consumer über eine Pipe verbinden
producer | consumerNamed Pipes ermöglichen die Kommunikation zwischen nicht verwandten Prozessen:
#!/bin/bash
FIFO_PATH="/tmp/myfifo"
# FIFO erstellen, falls sie nicht existiert
if [ ! -p "$FIFO_PATH" ]; then
mkfifo "$FIFO_PATH"
fi
# Aufräumen beim Beenden
trap 'rm -f "$FIFO_PATH"' EXIT
# Beispiel: Einfacher Server mit Named Pipe
echo "Server gestartet. Senden Sie Nachrichten an $FIFO_PATH"
echo "Beenden mit Ctrl+C"
while true; do
if read -r message < "$FIFO_PATH"; then
echo "Empfangen: $message"
# Echo-Antwort (muss in separater Shell erfolgen, um Blockierung zu vermeiden)
(echo "ECHO: $message" > "$FIFO_PATH") &
fi
doneEin Client kann mit dem Server kommunizieren:
#!/bin/bash
FIFO_PATH="/tmp/myfifo"
# Prüfen, ob FIFO existiert
if [ ! -p "$FIFO_PATH" ]; then
echo "Server nicht aktiv (FIFO nicht gefunden)"
exit 1
fi
# Nachricht senden
echo "Nachricht vom Client: $(date)" > "$FIFO_PATH"
# Auf Antwort warten (mit Timeout)
if read -t 5 response < "$FIFO_PATH"; then
echo "Antwort vom Server: $response"
else
echo "Timeout beim Warten auf Antwort"
fiFür eine vollständige bidirektionale Kommunikation werden zwei FIFOs benötigt:
#!/bin/bash
# Server-Skript
SERVER_TO_CLIENT="/tmp/server_to_client"
CLIENT_TO_SERVER="/tmp/client_to_server"
# FIFOs erstellen
mkfifo "$SERVER_TO_CLIENT" "$CLIENT_TO_SERVER"
# Aufräumen beim Beenden
cleanup() {
echo "Server wird beendet"
rm -f "$SERVER_TO_CLIENT" "$CLIENT_TO_SERVER"
exit 0
}
trap cleanup EXIT INT TERM
echo "Server gestartet"
# Hauptschleife für Anfragen
while true; do
if read request < "$CLIENT_TO_SERVER"; then
echo "Anfrage empfangen: $request"
# Antwort verarbeiten und zurücksenden
response="Server hat '$request' verarbeitet um $(date)"
echo "$response" > "$SERVER_TO_CLIENT"
fi
doneDer entsprechende Client:
#!/bin/bash
# Client-Skript
SERVER_TO_CLIENT="/tmp/server_to_client"
CLIENT_TO_SERVER="/tmp/client_to_server"
# Prüfen, ob FIFOs existieren
if [ ! -p "$SERVER_TO_CLIENT" ] || [ ! -p "$CLIENT_TO_SERVER" ]; then
echo "Server nicht aktiv (FIFOs nicht gefunden)"
exit 1
fi
# Funktion zum Senden einer Anfrage und Empfangen der Antwort
send_request() {
local request=$1
# Anfrage senden
echo "$request" > "$CLIENT_TO_SERVER"
# Auf Antwort warten (mit Timeout)
if read -t 5 response < "$SERVER_TO_CLIENT"; then
echo "Antwort: $response"
else
echo "Timeout beim Warten auf Antwort"
fi
}
# Beispielanfragen senden
send_request "Hallo Server"
send_request "Wie spät ist es?"Temporäre Dateien können für einfache Kommunikation verwendet werden:
#!/bin/bash
# Temporäre Datei für die Kommunikation erstellen
TEMP_FILE=$(mktemp)
# Aufräumen beim Beenden
trap 'rm -f "$TEMP_FILE"' EXIT
# Prozess 1 schreibt in die temporäre Datei
{
echo "Daten von Prozess 1" > "$TEMP_FILE"
echo "Weitere Daten" >> "$TEMP_FILE"
echo "FERTIG" >> "$TEMP_FILE"
} &
# Prozess 2 liest aus der temporären Datei
{
# Warten, bis Daten verfügbar sind
while [ ! -s "$TEMP_FILE" ]; do
sleep 0.1
done
# Auf das Ende-Signal warten
until grep -q "FERTIG" "$TEMP_FILE"; do
sleep 0.1
done
# Daten verarbeiten (ohne die FERTIG-Zeile)
grep -v "FERTIG" "$TEMP_FILE"
} &
# Auf beide Prozesse warten
waitSignale eignen sich gut für einfache Synchronisationsanforderungen:
#!/bin/bash
# Signal-Handler für verschiedene Ereignisse
synchronize() {
case $1 in
start)
echo "Start-Signal empfangen, beginne Verarbeitung"
;;
pause)
echo "Pause-Signal empfangen, pausiere Verarbeitung"
;;
resume)
echo "Resume-Signal empfangen, setze Verarbeitung fort"
;;
stop)
echo "Stop-Signal empfangen, beende Verarbeitung"
exit 0
;;
esac
}
# Signal-Handler einrichten
trap 'synchronize start' USR1
trap 'synchronize pause' USR2
trap 'synchronize resume' HUP
trap 'synchronize stop' TERM
echo "Prozess gestartet mit PID: $$"
echo "Steuern Sie diesen Prozess mit:"
echo " kill -USR1 $$ # Start"
echo " kill -USR2 $$ # Pause"
echo " kill -HUP $$ # Resume"
echo " kill -TERM $$ # Stop"
# Hauptschleife
while true; do
sleep 1
doneImplementierung einer Barrier, an der mehrere Prozesse warten müssen:
#!/bin/bash
# Konfiguration
NUM_PROCESSES=4
BARRIER_FILE="/tmp/barrier_$$"
# Initialisierung der Barrier
echo "0" > "$BARRIER_FILE"
# Aufräumen beim Beenden
trap 'rm -f "$BARRIER_FILE"' EXIT
# Funktion für die Barrier-Synchronisation
barrier_wait() {
local id=$1
echo "Prozess $id erreicht die Barrier"
# Atomare Inkrementierung des Zählers (mit flock für Thread-Sicherheit)
(
flock -x 200
count=$(<"$BARRIER_FILE")
count=$((count + 1))
echo $count > "$BARRIER_FILE"
if [ $count -eq $NUM_PROCESSES ]; then
# Letzter Prozess erreicht die Barrier, Signal an alle senden
echo "Alle Prozesse haben die Barrier erreicht"
# Zähler zurücksetzen
echo "0" > "$BARRIER_FILE"
# Signal an die Prozessgruppe senden
kill -USR1 -$$
fi
) 200>"$BARRIER_FILE.lock"
# Auf das Signal warten, sofern es nicht der letzte Prozess war
if [ $count -lt $NUM_PROCESSES ]; then
echo "Prozess $id wartet an der Barrier"
# Warten auf das Signal
trap 'echo "Prozess $id überschreitet die Barrier"; trap - USR1' USR1
while true; do
sleep 0.1
done
fi
}
# Beispielprozesse starten
for i in $(seq 1 $NUM_PROCESSES); do
(
# Phase 1 der Verarbeitung
sleep $((RANDOM % 5 + 1))
echo "Prozess $i hat Phase 1 abgeschlossen"
# Auf alle Prozesse warten
barrier_wait $i
# Phase 2 der Verarbeitung
sleep $((RANDOM % 3 + 1))
echo "Prozess $i hat Phase 2 abgeschlossen"
) &
done
# Auf Abschluss aller Prozesse warten
waitMit netcat (nc) können Shell-Skripte über
Netzwerk-Sockets kommunizieren:
#!/bin/bash
# Server-Skript
server() {
local port=8888
echo "Server startet auf Port $port"
# Einfacher Echo-Server mit netcat
while true; do
nc -l $port | while read line; do
echo "Empfangen: $line"
echo "ECHO: $line" | nc -l $port
done
done
}
# Client-Skript
client() {
local host="localhost"
local port=8888
local message="Hallo vom Client: $(date)"
echo "Sende Nachricht an $host:$port: $message"
# Nachricht senden und Antwort empfangen
response=$(echo "$message" | nc $host $port)
echo "Antwort erhalten: $response"
}
# Verwendung:
# ./script.sh server # Startet den Server
# ./script.sh client # Sendet eine Nachricht als Client
case "$1" in
server)
server
;;
client)
client
;;
*)
echo "Verwendung: $0 {server|client}"
exit 1
;;
esacDBus ermöglicht die Kommunikation mit Systemdiensten und anderen Anwendungen:
#!/bin/bash
# Beispiel: Benachrichtigung über DBus senden
send_notification() {
local title="$1"
local message="$2"
gdbus call --session \
--dest org.freedesktop.Notifications \
--object-path /org/freedesktop/Notifications \
--method org.freedesktop.Notifications.Notify \
"Shell-Skript" 0 "dialog-information" \
"$title" "$message" "[]" "{}" 5000
}
# Beispiel: System herunterfahren über DBus
shutdown_system() {
gdbus call --system \
--dest org.freedesktop.login1 \
--object-path /org/freedesktop/login1 \
--method org.freedesktop.login1.Manager.PowerOff \
true
}
# Beispielverwendung
send_notification "Prozessabschluss" "Der langwierige Prozess wurde erfolgreich abgeschlossen."
# Shutdown-Beispiel (auskommentiert)
# shutdown_systemDer POSIX Shared Memory kann für schnelle IPC genutzt werden:
#!/bin/bash
# Gemeinsam genutzter Speicherbereich
SHM_FILE="/dev/shm/myshm_$$"
# Initialisierung
echo "0" > "$SHM_FILE"
chmod 600 "$SHM_FILE"
# Aufräumen beim Beenden
trap 'rm -f "$SHM_FILE"' EXIT
# Prozess 1: Inkrementiert den Zähler
(
for i in {1..5}; do
# Atomare Aktualisierung mit flock
(
flock -x 200
current=$(cat "$SHM_FILE")
echo "Prozess 1: Aktueller Wert: $current"
echo $((current + 1)) > "$SHM_FILE"
) 200>"$SHM_FILE.lock"
sleep 1
done
) &
# Prozess 2: Liest und meldet den Zähler
(
for i in {1..7}; do
# Atomares Lesen mit flock
(
flock -s 200
current=$(cat "$SHM_FILE")
echo "Prozess 2: Lese Wert: $current"
) 200>"$SHM_FILE.lock"
sleep 0.7
done
) &
# Auf beide Prozesse warten
waitEin einfacher Semaphor für die Zugriffskontrolle auf gemeinsame Ressourcen:
#!/bin/bash
# Semaphor-Datei
SEM_FILE="/tmp/semaphore_$$"
# Semaphor initialisieren (Wert = Anzahl erlaubter gleichzeitiger Zugriffe)
echo "3" > "$SEM_FILE"
chmod 600 "$SEM_FILE"
# Aufräumen beim Beenden
trap 'rm -f "$SEM_FILE"' EXIT
# Funktion zum Erwerben des Semaphors
sem_acquire() {
local timeout=${1:-30} # Standardtimeout: 30 Sekunden
local start_time=$(date +%s)
while true; do
# Atomarer Zugriff auf den Semaphor
(
flock -x 200
# Aktuellen Wert lesen
local count=$(cat "$SEM_FILE")
if [ $count -gt 0 ]; then
# Semaphor erfolgreich erworben
echo $((count - 1)) > "$SEM_FILE"
return 0
fi
) 200>"$SEM_FILE.lock"
# Prüfen, ob das Timeout erreicht wurde
if [ $(($(date +%s) - start_time)) -ge $timeout ]; then
return 1 # Timeout
fi
# Kurz warten, bevor erneut versucht wird
sleep 0.1
done
}
# Funktion zum Freigeben des Semaphors
sem_release() {
# Atomare Inkrementierung
(
flock -x 200
local count=$(cat "$SEM_FILE")
echo $((count + 1)) > "$SEM_FILE"
) 200>"$SEM_FILE.lock"
}
# Beispiel: Mehrere Prozesse, die auf eine begrenzte Ressource zugreifen
for i in {1..10}; do
(
echo "Prozess $i versucht, den Semaphor zu erwerben"
if sem_acquire 5; then
echo "Prozess $i hat den Semaphor erworben"
# Kritischer Abschnitt
sleep $((RANDOM % 3 + 1))
echo "Prozess $i arbeitet im kritischen Abschnitt"
# Semaphor freigeben
sem_release
echo "Prozess $i hat den Semaphor freigegeben"
else
echo "Prozess $i: Timeout beim Erwerben des Semaphors"
fi
) &
done
# Auf alle Prozesse warten
waitDie effektive Verwaltung von Systemressourcen ist ein entscheidender Aspekt professioneller Shell-Skripte, insbesondere bei rechenintensiven oder langlebigen Prozessen. Dieser Abschnitt behandelt Techniken und Werkzeuge zum Überwachen, Steuern und Optimieren der Ressourcennutzung von Prozessen in Shell-Skripten.
Der ulimit-Befehl ermöglicht die Kontrolle der
Ressourcen, die einem Prozess und seinen Kindprozessen zur Verfügung
stehen:
#!/bin/bash
# Aktuelle Ressourcenlimits anzeigen
echo "Aktuelle Ressourcenlimits:"
ulimit -a
# Maximale Anzahl offener Dateien erhöhen
echo "Erhöhe das Limit für offene Dateien auf 4096"
ulimit -n 4096
# Maximale Dateigröße begrenzen (in Kilobytes)
echo "Begrenze die maximale Dateigröße auf 100 MB"
ulimit -f 102400
# Limitierte Core-Dump-Größe für besseres Debugging
echo "Setze Core-Dump-Größe auf 0"
ulimit -c 0
# Speicherbegrenzung (in Kilobytes)
echo "Begrenze virtuellen Speicher auf 2 GB"
ulimit -v 2097152
# CPU-Zeit begrenzen (in Sekunden)
echo "Begrenze CPU-Zeit auf 60 Sekunden"
ulimit -t 60
# Prozess mit diesen Limits starten
echo "Starte Prozess mit den konfigurierten Limits"
resource_intensive_commandWichtige ulimit-Optionen:
| Option | Beschreibung |
|---|---|
| -t | Maximale CPU-Zeit in Sekunden |
| -f | Maximale Dateigröße in Kilobytes |
| -d | Maximaler Prozess-Datensegment |
| -s | Maximale Stack-Größe |
| -c | Maximale Core-Datei-Größe |
| -v | Maximaler virtueller Speicher |
| -n | Maximale Anzahl offener Dateien |
| -u | Maximale Anzahl von Benutzerprozessen |
| -m | Maximale Speichernutzung |
Die Befehle nice und renice ermöglichen die
Steuerung der Prozesspriorität:
#!/bin/bash
# Prozess mit niedriger Priorität starten
echo "Starte rechenintensiven Prozess mit niedriger Priorität"
nice -n 19 ./intensive_calculation.sh &
PID_LOW=$!
# Prozess mit normaler Priorität starten
echo "Starte rechenintensiven Prozess mit normaler Priorität"
./intensive_calculation.sh &
PID_NORMAL=$!
# Prozess mit hoher Priorität starten (erfordert root-Rechte)
echo "Starte rechenintensiven Prozess mit hoher Priorität"
if [ $(id -u) -eq 0 ]; then
nice -n -10 ./intensive_calculation.sh &
PID_HIGH=$!
else
echo "Erhöhte Priorität erfordert root-Rechte"
fi
# Priorität eines laufenden Prozesses ändern
echo "Ändere Priorität eines laufenden Prozesses"
renice +5 -p $PID_NORMALDie nice-Werte reichen von -20 (höchste Priorität) bis
19 (niedrigste Priorität). Nur Benutzer mit entsprechenden Rechten
können Prozesse mit negativem Nice-Wert ausführen.
Der ionice-Befehl steuert die I/O-Scheduling-Klasse und
-Priorität:
#!/bin/bash
# Prüfen, ob ionice verfügbar ist
if ! command -v ionice &> /dev/null; then
echo "ionice ist nicht installiert"
exit 1
fi
# Prozess mit niedriger I/O-Priorität starten
echo "Starte I/O-intensiven Prozess mit niedriger Priorität"
ionice -c 3 ./io_intensive_task.sh &
PID_LOW=$!
# Prozess mit normaler I/O-Priorität (Best-Effort)
echo "Starte I/O-intensiven Prozess mit normaler Priorität"
ionice -c 2 -n 4 ./io_intensive_task.sh &
PID_NORMAL=$!
# Prozess mit hoher I/O-Priorität (erfordert root-Rechte)
echo "Starte I/O-intensiven Prozess mit hoher Priorität"
if [ $(id -u) -eq 0 ]; then
ionice -c 1 -n 0 ./io_intensive_task.sh &
PID_HIGH=$!
else
echo "Echtzeitklasse erfordert root-Rechte"
fiDie I/O-Scheduling-Klassen sind: - 1: Echtzeit (höchste Priorität, nur root) - 2: Best-Effort (Standard, mit Prioritäten 0-7) - 3: Idle (niedrigste Priorität, nur wenn keine andere I/O-Aktivität vorhanden ist)
Control Groups (cgroups) bieten eine leistungsstarke Methode zur Begrenzung, Zuweisung und Überwachung von Systemressourcen:
#!/bin/bash
# Prüfen, ob cgcreate verfügbar ist
if ! command -v cgcreate &> /dev/null; then
echo "cgcreate ist nicht verfügbar. Installieren Sie libcgroup-tools."
exit 1
fi
# Name der Kontrollgruppe
CGROUP_NAME="mygroup"
# Kontrollgruppe erstellen (erfordert root-Rechte)
if [ $(id -u) -eq 0 ]; then
# CPU-Kontrolle
cgcreate -g cpu:/$CGROUP_NAME
# Begrenze auf 50% der CPU-Zeit
echo 50000 > /sys/fs/cgroup/cpu/$CGROUP_NAME/cpu.cfs_quota_us
echo 100000 > /sys/fs/cgroup/cpu/$CGROUP_NAME/cpu.cfs_period_us
# Speicherkontrolle
cgcreate -g memory:/$CGROUP_NAME
# Begrenze auf 1 GB Speicher
echo 1073741824 > /sys/fs/cgroup/memory/$CGROUP_NAME/memory.limit_in_bytes
# Prozess in der Kontrollgruppe ausführen
cgexec -g cpu,memory:/$CGROUP_NAME ./resource_intensive_process.sh
# Kontrollgruppe aufräumen
cgdelete -g cpu,memory:/$CGROUP_NAME
else
echo "cgroup-Manipulation erfordert root-Rechte"
fiFür systemd-basierte Systeme kann systemd-run verwendet
werden:
#!/bin/bash
# Temporäre Unit mit Ressourcenbegrenzungen erstellen und Prozess starten
if command -v systemd-run &> /dev/null; then
echo "Starte Prozess mit systemd-run und Ressourcenbegrenzungen"
systemd-run --user --scope \
--property=CPUQuota=50% \
--property=MemoryLimit=1G \
--property=IOWeight=100 \
./resource_intensive_process.sh
else
echo "systemd-run ist nicht verfügbar"
fi#!/bin/bash
# Prozess im Hintergrund starten
./intensive_process.sh &
PROCESS_PID=$!
# Prozess mit top überwachen
echo "Überwache Prozess mit PID $PROCESS_PID"
top -p $PROCESS_PID
# Alternativ mit htop (falls installiert)
if command -v htop &> /dev/null; then
htop -p $PROCESS_PID
fi#!/bin/bash
# Funktion zum Überwachen der Ressourcennutzung eines Prozesses
monitor_process() {
local pid=$1
local interval=${2:-5} # Standardintervall: 5 Sekunden
local log_file=${3:-"process_stats.log"}
# Header für die Logdatei
echo "Timestamp,PID,CPU%,MEM%,VSZ,RSS,TIME" > "$log_file"
echo "Überwache Prozess $pid, Intervall: ${interval}s, Log: $log_file"
# Überwachungsschleife
while kill -0 $pid 2>/dev/null; do
# Prozessstatistiken sammeln
local stats=$(ps -p $pid -o pid,pcpu,pmem,vsz,rss,time --no-headers)
if [ -n "$stats" ]; then
# Zeitstempel hinzufügen und in die Logdatei schreiben
local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
echo "$timestamp,$(echo $stats | tr -s ' ' ',')" >> "$log_file"
fi
sleep $interval
done
echo "Prozess $pid existiert nicht mehr. Überwachung beendet."
}
# Beispielverwendung
./intensive_process.sh &
PROCESS_PID=$!
# Prozess überwachen (alle 2 Sekunden)
monitor_process $PROCESS_PID 2 "process_stats.log" &
MONITOR_PID=$!
# Main-Prozess beenden, wenn der zu überwachende Prozess beendet ist
wait $PROCESS_PID
kill $MONITOR_PID#!/bin/bash
# Speichernutzung eines Prozesses überwachen
monitor_memory() {
local pid=$1
local threshold_mb=$2
local interval=${3:-5}
echo "Überwache Speichernutzung für PID $pid (Schwellenwert: $threshold_mb MB)"
while kill -0 $pid 2>/dev/null; do
# Speichernutzung in KB
local mem_kb=$(ps -p $pid -o rss= 2>/dev/null)
if [ -n "$mem_kb" ]; then
local mem_mb=$((mem_kb / 1024))
echo "PID $pid: Speichernutzung: $mem_mb MB"
# Schwellenwert prüfen
if [ $mem_mb -gt $threshold_mb ]; then
echo "WARNUNG: Speichernutzung überschreitet Schwellenwert ($threshold_mb MB)"
# Optional: Maßnahmen ergreifen, z.B.
# kill -TERM $pid
fi
fi
sleep $interval
done
}
# Beispielverwendung
./memory_intensive_process.sh &
PROCESS_PID=$!
# Speichernutzung überwachen mit 500 MB Schwellenwert
monitor_memory $PROCESS_PID 500 2Ein fortgeschrittenes Skript zur adaptiven Ressourcenzuweisung basierend auf der Systemlast:
#!/bin/bash
# Konfiguration
LOAD_THRESHOLD_HIGH=4.0
LOAD_THRESHOLD_LOW=2.0
CHECK_INTERVAL=30 # Sekunden
# Funktionen zur Prozesssteuerung
throttle_process() {
local pid=$1
echo "System unter hoher Last. Drossele Prozess $pid"
renice +10 -p $pid
# Für I/O-Drosselung (falls verfügbar)
command -v ionice &>/dev/null && ionice -c 3 -p $pid
}
unthrottle_process() {
local pid=$1
echo "System unter niedriger Last. Normalisiere Prozess $pid"
renice 0 -p $pid
# Für I/O-Normalisierung (falls verfügbar)
command -v ionice &>/dev/null && ionice -c 2 -n 4 -p $pid
}
# Hauptprozess starten
echo "Starte Hauptprozess"
./main_process.sh &
MAIN_PID=$!
# Niedrigere Prioritätsprozesse starten
echo "Starte Hintergrundprozesse"
for i in {1..3}; do
./background_process.sh &
BG_PIDS+=($!)
done
# Überwachungsschleife
echo "Starte adaptive Ressourcenüberwachung"
throttled=false
while kill -0 $MAIN_PID 2>/dev/null; do
# Aktuelle Systemlast ermitteln (1-Minuten-Load)
load=$(cat /proc/loadavg | cut -d ' ' -f 1)
echo "Aktuelle Systemlast: $load"
# Load-Schwellenwerte prüfen und Maßnahmen ergreifen
if (( $(echo "$load > $LOAD_THRESHOLD_HIGH" | bc -l) )); then
if [ "$throttled" = false ]; then
echo "Hohe Systemlast erkannt. Drossele Hintergrundprozesse"
for pid in "${BG_PIDS[@]}"; do
if kill -0 $pid 2>/dev/null; then
throttle_process $pid
fi
done
throttled=true
fi
elif (( $(echo "$load < $LOAD_THRESHOLD_LOW" | bc -l) )); then
if [ "$throttled" = true ]; then
echo "Niedrige Systemlast erkannt. Normalisiere Hintergrundprozesse"
for pid in "${BG_PIDS[@]}"; do
if kill -0 $pid 2>/dev/null; then
unthrottle_process $pid
fi
done
throttled=false
fi
fi
sleep $CHECK_INTERVAL
done
# Aufräumen
echo "Hauptprozess beendet. Beende Hintergrundprozesse"
for pid in "${BG_PIDS[@]}"; do
kill $pid 2>/dev/null
doneDas cpulimit-Tool begrenzt die CPU-Nutzung eines
Prozesses auf einen bestimmten Prozentsatz:
#!/bin/bash
# Prüfen, ob cpulimit verfügbar ist
if ! command -v cpulimit &> /dev/null; then
echo "cpulimit ist nicht installiert"
echo "Installieren Sie es mit: apt-get install cpulimit (Debian/Ubuntu)"
exit 1
fi
# Prozess starten und seine CPU-Nutzung begrenzen
echo "Starte CPU-intensiven Prozess mit Begrenzung auf 30% CPU"
./cpu_intensive_process.sh &
PROCESS_PID=$!
# CPU-Nutzung auf 30% begrenzen
cpulimit -p $PROCESS_PID -l 30 &
CPULIMIT_PID=$!
# Auf Abschluss des Hauptprozesses warten
wait $PROCESS_PID
# cpulimit beenden
kill $CPULIMIT_PID 2>/dev/nullDas sar-Tool (Teil des sysstat-Pakets) bietet
detaillierte Systemressourcenstatistiken:
#!/bin/bash
# Prüfen, ob sar verfügbar ist
if ! command -v sar &> /dev/null; then
echo "sar ist nicht installiert"
echo "Installieren Sie es mit: apt-get install sysstat (Debian/Ubuntu)"
exit 1
fi
# Systemressourcen vor dem Prozessstart erfassen
echo "Erfasse Baseline-Systemressourcen"
sar -o before.sar 1 5 >/dev/null
# Prozess starten
echo "Starte den zu überwachenden Prozess"
./resource_intensive_process.sh &
PROCESS_PID=$!
# Ressourcen während der Prozessausführung überwachen
echo "Überwache Systemressourcen während der Prozessausführung"
sar -o during.sar 1 30 >/dev/null &
SAR_PID=$!
# Auf Prozessabschluss warten
wait $PROCESS_PID
# sar-Überwachung beenden
kill $SAR_PID 2>/dev/null
sleep 2
# Systemressourcen nach dem Prozessende erfassen
echo "Erfasse Systemressourcen nach Prozessende"
sar -o after.sar 1 5 >/dev/null
# Zusammenfassung erstellen
echo "Erstelle Ressourcennutzungsbericht"
echo "=== CPU-Nutzung ===" > resource_report.txt
echo "Vor der Ausführung:" >> resource_report.txt
sar -u -f before.sar | tail -n 6 >> resource_report.txt
echo "Während der Ausführung:" >> resource_report.txt
sar -u -f during.sar | tail -n 6 >> resource_report.txt
echo "Nach der Ausführung:" >> resource_report.txt
sar -u -f after.sar | tail -n 6 >> resource_report.txt
echo "=== Speichernutzung ===" >> resource_report.txt
echo "Vor der Ausführung:" >> resource_report.txt
sar -r -f before.sar | tail -n 6 >> resource_report.txt
echo "Während der Ausführung:" >> resource_report.txt
sar -r -f during.sar | tail -n 6 >> resource_report.txt
echo "Nach der Ausführung:" >> resource_report.txt
sar -r -f after.sar | tail -n 6 >> resource_report.txt
echo "Bericht wurde in resource_report.txt geschrieben"Der unshare-Befehl kann zur einfachen Prozessisolierung
verwendet werden:
#!/bin/bash
# Prüfen, ob unshare verfügbar ist
if ! command -v unshare &> /dev/null; then
echo "unshare ist nicht verfügbar"
exit 1
fi
# Prozess mit eigenem Prozess- und Netzwerk-Namespace ausführen (erfordert root)
if [ $(id -u) -eq 0 ]; then
echo "Starte Prozess in isolierter Umgebung"
unshare --fork --pid --net --mount-proc /bin/bash -c './isolated_process.sh'
else
echo "Namespace-Isolation erfordert root-Rechte"
fiMehrere zusammengehörige Prozesse können als Prozessgruppe verwaltet werden:
#!/bin/bash
# Funktionen zur Prozessgruppenverwaltung
create_process_group() {
# Neue Sitzung und Prozessgruppe erstellen
setsid bash -c "
# PID der Prozessgruppe speichern
echo \$$ > $1
# Prozesse in dieser Gruppe starten
$2 &
$3 &
$4 &
# Warten, bis alle Prozesse beendet sind
wait
" &
# Kurz warten, damit die PGID-Datei geschrieben werden kann
sleep 1
}
set_group_priority() {
local pgid_file=$1
local nice_value=$2
if [ -f "$pgid_file" ]; then
local pgid=$(cat "$pgid_file")
echo "Setze Priorität für Prozessgruppe $pgid auf $nice_value"
renice $nice_value -g $pgid
else
echo "PGID-Datei $pgid_file nicht gefunden"
fi
}
# Beispielverwendung
PGID_FILE="/tmp/mygroup.pgid"
echo "Erstelle Prozessgruppe mit mehreren Prozessen"
create_process_group "$PGID_FILE" \
"./process1.sh" \
"./process2.sh" \
"./process3.sh"
# Priorität der gesamten Gruppe ändern
sleep 5
set_group_priority "$PGID_FILE" 10
# Auf Benutzereingabe warten, um Prozessgruppe zu beenden
read -p "Drücken Sie Enter, um die Prozessgruppe zu beenden"
if [ -f "$PGID_FILE" ]; then
kill -TERM -$(cat "$PGID_FILE")
rm -f "$PGID_FILE"
fiProzess-Hierarchie beachten:
# Ressourcenlimits für Hauptprozess und Kindprozesse setzen
ulimit -v 2097152 # Virtuellen Speicher auf 2 GB begrenzen
./parent_process.shFeedback-basierte Ressourcenzuweisung:
# Prozessgeschwindigkeit basierend auf Systemlast anpassen
while true; do
load=$(cat /proc/loadavg | cut -d ' ' -f 1)
if (( $(echo "$load > 3.0" | bc -l) )); then
# System ist stark ausgelastet, Verarbeitungsrate reduzieren
export BATCH_SIZE=10
else
# System ist nicht stark ausgelastet, Verarbeitungsrate erhöhen
export BATCH_SIZE=100
fi
./process_batch.sh $BATCH_SIZE
sleep 5
doneKritische vs. Hintergrundprozesse:
# Kritische Prozesse mit höherer Priorität
nice -n -10 ./critical_process.sh &
# Hintergrundprozesse mit niedrigerer Priorität
nice -n 19 ./background_process.sh &Überwachung mit Alarmierung:
#!/bin/bash
# Funktion zur Benachrichtigung bei Ressourcenproblemen
notify_admin() {
local subject="$1"
local message="$2"
echo "$message" | mail -s "$subject" admin@example.com
}
# Prozess überwachen
while kill -0 $PROCESS_PID 2>/dev/null; do
# CPU-Nutzung prüfen
cpu_usage=$(ps -p $PROCESS_PID -o pcpu= | tr -d ' ')
# Speichernutzung prüfen (RSS in KB)
mem_usage=$(ps -p $PROCESS_PID -o rss= | tr -d ' ')
mem_usage_mb=$((mem_usage / 1024))
if (( $(echo "$cpu_usage > 90.0" | bc -l) )); then
notify_admin "Hohe CPU-Nutzung" "Prozess $PROCESS_PID verwendet $cpu_usage% CPU"
fi
if [ $mem_usage_mb -gt 1000 ]; then
notify_admin "Hohe Speichernutzung" "Prozess $PROCESS_PID verwendet $mem_usage_mb MB Speicher"
fi
sleep 60
doneGraceful Degradation:
#!/bin/bash
# Funktionsweise basierend auf verfügbaren Ressourcen anpassen
check_resources() {
# Verfügbarer Speicher in MB
local mem_available=$(free -m | awk '/^Mem:/ {print $7}')
if [ $mem_available -lt 200 ]; then
echo "CRITICAL: Weniger als 200 MB Speicher verfügbar"
export MODE="minimal"
elif [ $mem_available -lt 500 ]; then
echo "WARNING: Weniger als 500 MB Speicher verfügbar"
export MODE="reduced"
else
echo "INFO: Ausreichend Speicher verfügbar"
export MODE="full"
fi
}
# Verarbeitungsfunktion mit verschiedenen Modi
process_data() {
case $MODE in
minimal)
echo "Ausführung im minimalen Modus"
./process_minimal.sh
;;
reduced)
echo "Ausführung im reduzierten Modus"
./process_reduced.sh
;;
full|*)
echo "Ausführung im vollen Modus"
./process_full.sh
;;
esac
}
# Hauptschleife
while true; do
check_resources
process_data
sleep 10
done