wait -n für paralleles Job Harvesting in Bash
AI generated
$_
#!/
Bash · wait -n · Parallelisierung · Job Harvesting
wait -n für paralleles Job Harvesting in Bash
konstante Parallelität statt starrem Batch-Warten

Klassisches wait blockiert, bis alle gestarteten Hintergrundjobs fertig sind, was die Parallelität auf ganze Batches begrenzt. wait -n wartet stattdessen auf den ersten Job, der fertig wird, egal welcher, und macht damit Worker-Pools mit konstant hoher Auslastung erst wirklich praktikabel, ohne auf externe Werkzeuge wie GNU Parallel zurückgreifen zu müssen.

16 Min. Lesezeit wait -n · Worker Pool · Exit Codes · Timeouts Bash 4.3+ · 5.x · Linux

1. Das Problem beim klassischen parallelen Warten

Wer in Bash mehrere Hintergrundjobs startet und danach schlicht wait ohne Argument aufruft, blockiert, bis wirklich jeder gestartete Job beendet ist. Das klingt zunächst harmlos, führt aber bei ungleich langen Jobs zu ineffizienter Auslastung: Ein einzelner langsamer Job hält die gesamte Batch-Grenze offen, während schnellere Jobs längst fertig sind und die freigewordene Kapazität ungenutzt bleibt, bis der gesamte Batch durchgelaufen ist.

Das klassische Muster, um Parallelität zu begrenzen, startet Jobs in festen Batches: eine feste Anzahl parallel starten, mit wait auf das ganze Batch warten, nächstes Batch starten. Dieses Muster funktioniert, verschenkt aber Laufzeit, sobald die Jobs innerhalb eines Batches unterschiedlich lange brauchen. wait -n löst genau dieses Problem, indem es zurückkehrt, sobald irgendein Hintergrundjob fertig wird, egal welcher, sodass sofort ein neuer Job nachgestartet werden kann.

Mit wait -n lässt sich damit ein echtes Worker-Pool-Muster in reinem Bash umsetzen, bei dem die Anzahl gleichzeitig laufender Jobs konstant auf einem gewünschten Maximum gehalten wird, ohne auf GNU Parallel oder xargs -P zurückgreifen zu müssen. Die folgenden Abschnitte zeigen die Syntax, das Einsammeln von Exit-Codes und das vollständige Worker-Pool-Pattern im Detail.

2. wait -n Grundlagen: worauf es genau wartet

Das Flag -n wurde in Bash 4.3 zum wait-Builtin hinzugefügt und ändert dessen Verhalten fundamental: Statt auf alle angegebenen Jobs zu warten, kehrt wait -n zurück, sobald der erste von ihnen beendet ist. Ohne weitere Argumente bezieht sich wait -n auf alle aktuell laufenden Hintergrundjobs der Shell, was es zum idealen Baustein für Schleifen macht, die kontinuierlich neue Arbeit nachschieben wollen, sobald Kapazität frei wird.

Der Rückgabewert von wait -n ist der Exit-Code des zuerst beendeten Jobs, nicht automatisch dessen PID. Um herauszufinden, welcher Job das war, muss man entweder mit jobs -l vor und nach dem Aufruf vergleichen, oder, ab Bash 5.1, wait -n mit einer expliziten PID-Liste kombinieren und den Rückgabewert der Shell-Variable prüfen. Dieser Unterschied zwischen "irgendein Job" und "welcher Job genau" ist der zentrale Stolperstein beim ersten Einsatz von wait -n.


#!/usr/bin/env bash
set -euo pipefail

# Start three jobs with different durations
sleep 1 &
sleep 3 &
sleep 5 &

echo "Active jobs: $(jobs -r | wc -l)"

# Classic "wait" blocks until ALL jobs are done (up to 5s here)
# wait

# "wait -n" returns as soon as the FIRST job finishes (after ~1s)
wait -n
echo "First job finished after ~1s, remaining: $(jobs -r | wc -l)"

wait -n
echo "Second job finished after ~3s total, remaining: $(jobs -r | wc -l)"

wait -n
echo "Third job finished after ~5s total, remaining: $(jobs -r | wc -l)"

3. Exit-Codes mit wait -n einsammeln

Der Exit-Code, den wait -n zurückgibt, gehört zu genau dem Job, der als erstes beendet wurde, unabhängig davon, in welcher Reihenfolge die Jobs gestartet wurden. Für Skripte, die wissen müssen, ob alle parallelen Jobs erfolgreich waren, reicht es, den Rückgabewert von wait -n in jeder Iteration zu prüfen und bei einem Fehlercode ungleich null einen Zähler zu erhöhen, statt sofort abzubrechen.

Wichtig ist, wait -n in einer Schleife so oft aufzurufen, wie Jobs gestartet wurden, sonst bleiben unbeendete Jobs als Zombies zurück, bis das Skript selbst endet. Die Anzahl noch laufender Jobs lässt sich jederzeit mit jobs -r | wc -l oder, robuster, mit einem mitgeführten Zähler ermitteln, der bei jedem Start erhöht und bei jedem wait -n verringert wird.


#!/usr/bin/env bash
set -euo pipefail

declare -a exit_codes=()
job_count=0

for url in "${urls[@]}"; do
  curl -fsS -o "/dev/null" "$url" &
  ((job_count++))
done

failed=0
for ((i = 0; i < job_count; i++)); do
  if ! wait -n; then
    ((failed++))
  fi
done

echo "Finished: $job_count jobs, failed: $failed"
if (( failed > 0 )); then
  exit 1
fi

4. Worker-Pool-Pattern: konstante Parallelität halten

Das eigentliche Kraftstück von wait -n zeigt sich im Worker-Pool-Pattern: Statt in festen Batches zu arbeiten, wird ein neuer Job genau dann gestartet, wenn die Anzahl laufender Jobs unter das Maximum fällt. Dieses Muster hält die Auslastung konstant nahe am gewünschten Parallelitätsgrad, unabhängig davon, wie unterschiedlich lange einzelne Jobs brauchen, was in Batches mit stark schwankenden Laufzeiten einen erheblichen Geschwindigkeitsvorteil gegenüber starrem Batch-Warten bringt.

Die Implementierung braucht dafür nur einen Zähler für aktive Jobs und eine einfache Bedingung: Solange der Zähler das Maximum erreicht hat, wird mit wait -n gewartet, bevor der nächste Job gestartet wird. Dieses Muster ist deutlich kompakter als das klassische Batch-Array-Pattern mit expliziter PID-Verwaltung und liefert gleichzeitig bessere Auslastung.


#!/usr/bin/env bash
set -euo pipefail

MAX_JOBS=4
active=0
failed=0

process_file() {
  local file="$1"
  gzip -9 "$file"
}

for file in /var/log/archive/*.log; do
  process_file "$file" &
  ((active++))

  # Throttle: once at max capacity, wait for ANY job to finish first
  if (( active >= MAX_JOBS )); then
    wait -n || ((failed++))
    ((active--))
  fi
done

# Drain remaining jobs after the loop
while (( active > 0 )); do
  wait -n || ((failed++))
  ((active--))
done

echo "Done. Failed jobs: $failed"

5. wait -n mit PID-Liste: gezielt auf bestimmte Jobs warten

Ab Bash 5.1 akzeptiert wait -n zusätzlich eine explizite Liste von PIDs oder Jobspecs und wartet dann nur auf das erste Ende innerhalb dieser Teilmenge, statt auf alle Hintergrundjobs der Shell. Das ist besonders nützlich, wenn ein Skript mehrere unabhängige Gruppen von Hintergrundjobs gleichzeitig verwaltet, etwa eine Gruppe für Downloads und eine für Datenbank-Migrationen, die getrennt voneinander überwacht werden sollen.

Mit einer PID-Liste liefert wait -n "${pids[@]}" zusätzlich die PID des beendeten Jobs über die Shell-Variable $! zurück, wenn man vor dem Aufruf die Liste durchsucht, oder man kombiniert dies mit einer Prüfung, welche PID aus der Liste nicht mehr in jobs -r auftaucht. Dieser gezieltere Einsatz von wait -n lohnt sich, sobald mehrere Job-Gruppen unabhängige Fehlerbehandlung benötigen.


#!/usr/bin/env bash
set -euo pipefail

declare -a download_pids=()
declare -a migration_pids=()

for url in "${download_urls[@]}"; do
  curl -fsS -O "$url" &
  download_pids+=($!)
done

for db in "${migration_dbs[@]}"; do
  ./migrate.sh "$db" &
  migration_pids+=($!)
done

# Bash 5.1+: wait -n on an explicit subset of PIDs
while (( ${#download_pids[@]} > 0 )); do
  wait -n "${download_pids[@]}"
  # Rebuild the list, dropping PIDs that are no longer running
  download_pids=($(jobs -p))
done

6. Timeouts kombinieren: wait -n -t

Bash 5.1 führte zusätzlich das Flag -t für wait ein, das einen Timeout in Sekunden setzt. Kombiniert mit -n wartet wait -n -t Sekunden auf den ersten fertigen Job, gibt aber die Kontrolle zurück, wenn innerhalb der angegebenen Zeit kein Job endet. Der Exit-Code 128 signalisiert dabei speziell, dass der Timeout erreicht wurde, nicht dass ein Job fehlgeschlagen ist, ein Unterschied, den Skripte explizit prüfen müssen.

Dieses Timeout-Verhalten ist wichtig für Worker-Pools, die auch bei hängenden Jobs nicht unbegrenzt blockieren sollen, etwa wenn ein einzelner Netzwerk-Request nie zurückkehrt. Ohne Timeout würde wait -n in so einem Fall den gesamten Fortschritt des Skripts lahmlegen, bis der hängende Job manuell beendet wird.


#!/usr/bin/env bash
set -euo pipefail

MAX_JOBS=4
active=0

for host in "${hosts[@]}"; do
  ssh -o ConnectTimeout=5 "$host" "uptime" &
  ((active++))

  if (( active >= MAX_JOBS )); then
    # Bash 5.1+: bail out of waiting after 10s even if nothing finished
    if wait -n -t 10; then
      ((active--))
    else
      status=$?
      if (( status == 128 )); then
        echo "[WARN] Timeout waiting for a job, checking again" >&2
      else
        ((active--))
      fi
    fi
  fi
done

7. Fehlerbehandlung ohne den Rest abzubrechen

In vielen Batch-Verarbeitungsskripten soll ein einzelner fehlgeschlagener Job nicht das gesamte Skript abbrechen, sondern lediglich vermerkt werden, damit am Ende eine zusammenfassende Fehlermeldung erscheint. wait -n unterstützt dieses Muster direkt, weil sein Rückgabewert pro Job geprüft werden kann, ohne set -e sofort auszulösen, solange der Aufruf in einer if- oder ||-Konstruktion steht.

Für eine saubere Zusammenfassung empfiehlt sich, fehlgeschlagene Jobs mitsamt einer Kennung zu protokollieren, etwa dem Dateinamen oder der URL, die verarbeitet wurde. Da wait -n nur den Exit-Code, nicht automatisch den Kontext des Jobs liefert, muss diese Zuordnung manuell über eine Datenstruktur wie ein assoziatives Array von PID zu Kontext hergestellt werden, wenn detaillierte Fehlerberichte gebraucht werden.

8. Kompatibilität: Bash-Versionen und Fallbacks

wait -n selbst benötigt Bash 4.3, während die PID-Listen-Variante und das -t-Flag erst ab Bash 5.1 verfügbar sind. macOS liefert standardmäßig Bash 3.2 aus Lizenzgründen aus, weshalb wait -n dort ohne eine über Homebrew installierte neuere Bash-Version nicht funktioniert. Skripte, die auf mehreren Plattformen laufen sollen, müssen die Bash-Version zur Laufzeit prüfen, bevor sie wait -n verwenden.

Als Fallback für ältere Bash-Versionen bleibt nur das klassische Batch-Pattern mit einem PID-Array und explizitem Warten auf das älteste Element, wie es auch in Automatisierungsskripten vor Bash 4.3 üblich war. Dieser Fallback ist weniger effizient, aber portabel und funktioniert auch mit der auf macOS vorinstallierten Bash 3.2.

9. Job-Harvesting-Strategien im Vergleich

Die folgende Tabelle vergleicht die verschiedenen Strategien für paralleles Job-Harvesting in Bash.

Strategie Auslastung bei ungleichen Laufzeiten Mindest-Bash-Version Komplexität
wait ohne Argument (Batch) Schlecht Alle Versionen Niedrig
PID-Array + wait "${pids[0]}" Mittel Alle Versionen Mittel
wait -n (ohne PID-Liste) Sehr gut Bash 4.3+ Niedrig
wait -n mit PID-Liste + -t Sehr gut Bash 5.1+ Mittel
GNU Parallel / xargs -P Sehr gut Externes Tool Niedrig, aber Abhängigkeit

wait -n schließt damit genau die Lücke zwischen dem simplen, aber ineffizienten Batch-Pattern und externen Werkzeugen wie GNU Parallel, die eine zusätzliche Abhängigkeit bedeuten. Für Bash-Skripte ab Version 4.3 ist wait -n in den meisten Fällen die richtige Wahl für effizientes paralleles Job-Harvesting ohne Fremdabhängigkeiten.

Mironsoft

Shell-Automatisierung, DevOps-Tooling und Deployment-Infrastruktur

Parallele Batch-Jobs, die durch starres Warten Zeit verlieren?

Wir bauen Worker-Pool-Muster mit wait -n in eure Bash-Automatisierung ein, mit konstanter Auslastung, sauberer Fehlerbehandlung und Timeout-Absicherung für hängende Jobs, statt starrer Batch-Grenzen.

Parallelisierungs-Audit

Batch-Pattern auf wait -n Potenzial und Auslastung prüfen

Worker-Pool-Design

Konstante Parallelität mit Exit-Code-Sammlung und Timeouts

Kompatibilitäts-Check

Bash-Versionen prüfen und portable Fallbacks einbauen

10. Zusammenfassung

wait -n löst das Kernproblem starrer Batch-Verarbeitung, indem es auf den ersten fertigen Job wartet statt auf alle. Kombiniert mit einem einfachen Zähler entsteht ein Worker-Pool-Pattern, das die Parallelität konstant auf einem gewünschten Maximum hält, unabhängig davon, wie unterschiedlich lange einzelne Jobs brauchen. Der Rückgabewert von wait -n liefert dabei direkt den Exit-Code des jeweils beendeten Jobs.

Ab Bash 5.1 erweitern eine explizite PID-Liste und das -t-Flag wait -n um gezieltes Warten auf Teilmengen und Timeout-Absicherung gegen hängende Jobs. Für ältere Bash-Versionen bleibt das klassische Batch-Pattern als portabler, aber weniger effizienter Fallback. Wer regelmäßig parallele Verarbeitung in Bash-Skripten einsetzt, sollte wait -n als Standardwerkzeug etablieren, statt starrer Batch-Grenzen.

wait -n für paralleles Job Harvesting — Das Wichtigste auf einen Blick

Grundverhalten

wait -n wartet auf den ersten fertigen Hintergrundjob, statt blockierend auf alle. Verfügbar ab Bash 4.3.

Worker-Pool

Mit einem Zähler für aktive Jobs entsteht konstante Parallelität statt starrer Batch-Grenzen.

Timeout & PID-Liste

Ab Bash 5.1: wait -n -t Sekunden für Timeouts, explizite PID-Listen für gezieltes Warten.

Fallback

Für Bash vor 4.3 oder macOS-Standard-Bash: klassisches PID-Array mit Warten auf das älteste Element.

11. FAQ: wait -n in Bash

1Was macht wait -n genau?
Kehrt beim ersten fertigen Hintergrundjob zurück, statt auf alle zu warten, und liefert dessen Exit-Code.
2Ab welcher Bash-Version verfügbar?
Ab Bash 4.3. PID-Liste und -t Flag ab Bash 5.1.
3Wie baue ich einen Worker-Pool?
Zähler für aktive Jobs führen, bei Erreichen des Maximums mit wait -n auf freien Slot warten.
4Welcher Job wurde beendet?
Ohne PID-Liste nicht direkt erkennbar. Ab Bash 5.1 mit expliziter PID-Liste möglich.
5Was bedeutet Exit-Code 128?
Timeout erreicht, kein Job-Fehlschlag. Muss explizit von echten Fehlern unterschieden werden.
6Funktioniert es auf macOS?
Nicht mit der Standard-Bash 3.2. Neuere Bash-Version via Homebrew nötig.
7Warten auf Untermenge möglich?
Ja, ab Bash 5.1 mit expliziter PID-Liste als Argument.
8Verhindert es Skriptabbruch bei Fehler?
Ja, Rückgabewert prüfbar ohne sofortigen set -e Abbruch, solange in if oder || eingebettet.
9Vorteil gegenüber GNU Parallel?
Bash-Bordmittel ohne zusätzliche Abhängigkeit oder Installation.
10Fallback ohne wait -n?
Klassisches PID-Array mit Warten auf das älteste Element.