Named Pipes (FIFOs) in Bash: Prozesse ohne Zwischendatei verbinden
AI generated
$_
#!/
Bash · IPC · Linux · Prozesskommunikation
Named Pipes (FIFOs) in Bash
Prozesse verbinden, ohne eine Zwischendatei auf die Platte zu schreiben

Eine Named Pipe (FIFO) verhält sich im Dateisystem wie eine Datei, funktioniert aber wie eine klassische Pipe: Daten fließen direkt von einem Schreiber zu einem Leser, ohne jemals auf der Platte zu landen. Wer FIFOs richtig einsetzt, entkoppelt unabhängige Prozesse, baut Logging-Multiplexer und vermeidet Race Conditions, die bei Zwischendateien fast unvermeidlich sind.

17 Min. Lesezeit mkfifo · blockierendes I/O · trap Bash 4.x · 5.x · Linux · macOS

1. Was eine FIFO ist und wie sie sich von einer normalen Pipe unterscheidet

Eine Named Pipe (kurz FIFO, First In First Out) ist ein besonderer Dateityp, den das Dateisystem verwaltet, aber niemals tatsächlich Bytes auf die Platte schreibt. Im Unterschied zur anonymen Pipe, die eine Shell mit | zwischen zwei Befehlen erzeugt und die nur innerhalb einer Prozessfamilie existiert, hat eine FIFO einen sichtbaren Namen im Dateisystem und kann von völlig unabhängigen Prozessen geöffnet werden, solange diese Lese- und Schreibrechte auf den Pfad haben.

Das macht die FIFO zum richtigen Werkzeug, wenn zwei Skripte kommunizieren sollen, die nicht in derselben Pipe-Kette stehen, zum Beispiel ein Daemon, der Log-Zeilen schreibt, und ein zweites, unabhängig gestartetes Skript, das diese Zeilen live verarbeitet. Eine Zwischendatei würde hier funktionieren, bringt aber unnötige Festplatten-I/O, Race Conditions beim gleichzeitigen Lesen und Schreiben, und das Problem, alte Daten irgendwann aufzuräumen. Eine Named Pipe löst alle drei Probleme gleichzeitig, weil sie rein im Kernel-Puffer lebt.

2. mkfifo: eine benannte Pipe anlegen und ihre Lebensdauer verstehen

Das Kommando mkfifo erzeugt eine FIFO an einem beliebigen Pfad im Dateisystem. Ein ls -l zeigt sie mit dem Dateityp-Buchstaben p am Zeilenanfang, und ihre Größe bleibt konstant bei null, weil niemals Daten dauerhaft gespeichert werden. Die Lebensdauer der FIFO als Dateisystem-Eintrag ist unabhängig vom Datenfluss: Sie existiert weiter, bis sie explizit mit rm gelöscht wird, auch wenn kein Prozess gerade liest oder schreibt.

Ein häufiger Anfängerfehler ist, eine Named Pipe im temporären Verzeichnis anzulegen und zu vergessen, dass sie bei jedem erneuten Skriptlauf schon existieren könnte. Deshalb prüft ein robustes Bash-Pattern vor dem Anlegen, ob der Pfad schon eine FIFO ist, statt mkfifo blind aufzurufen und bei einem Fehler abzubrechen.


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

readonly PIPE_PATH="/tmp/mironsoft-log-pipe.$$"

# Idempotent creation: only create if it does not already exist as a FIFO
if [[ ! -p "$PIPE_PATH" ]]; then
  mkfifo -m 600 "$PIPE_PATH"
fi

# Always clean up, no matter how the script exits
trap 'rm -f "$PIPE_PATH"' EXIT

ls -l "$PIPE_PATH"
# prow------- 1 deploy deploy 0 Aug  6 10:00 /tmp/mironsoft-log-pipe.12345

3. Prozesse über eine FIFO verbinden: Schreiber und Leser entkoppeln

Sobald die FIFO existiert, kann ein beliebiger Prozess hineinschreiben und ein beliebiger anderer Prozess daraus lesen, solange beide denselben Pfad kennen. Das entkoppelt Schreiber und Leser vollständig voneinander: Sie müssen nicht zur selben Zeit gestartet werden, nicht denselben Elternprozess haben und nicht einmal in derselben Programmiersprache geschrieben sein. Ein Python-Daemon kann in dieselbe Named Pipe schreiben, aus der ein Bash-Skript liest.

Der Datenfluss selbst verhält sich exakt wie bei einer anonymen Pipe: Was der Schreiber mit > in die FIFO schickt, kommt beim Leser mit < in derselben Reihenfolge an, bis zu der Puffergröße des Kernels (typischerweise 64 KB unter Linux). Überschreitet der Schreiber diese Grenze, bevor der Leser liest, blockiert der Schreib-Aufruf, bis wieder Platz im Puffer ist. Das ist kein Fehler, sondern das gewünschte Backpressure-Verhalten einer FIFO.


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

readonly PIPE_PATH="/tmp/mironsoft-events.pipe"
[[ -p "$PIPE_PATH" ]] || mkfifo "$PIPE_PATH"

# Reader: runs in the background, processes every line as it arrives
process_events() {
  while IFS= read -r line; do
    echo "[EVENT] $line"
  done < "$PIPE_PATH"
}
process_events &
reader_pid=$!

# Writer: an unrelated process, started independently, feeds the FIFO
{
  echo "deploy.started"
  sleep 1
  echo "deploy.finished"
} > "$PIPE_PATH"

wait "$reader_pid"
rm -f "$PIPE_PATH"

4. Blockierendes Verhalten: warum ein einfaches open() auf einer FIFO hängen bleibt

Der wichtigste Unterschied zu einer normalen Datei zeigt sich beim Öffnen: Ein Prozess, der eine FIFO nur zum Lesen öffnet (< pipe), blockiert so lange, bis mindestens ein anderer Prozess sie zum Schreiben öffnet, und umgekehrt. Wer aus Versehen ein Skript schreibt, das eine FIFO liest, ohne dass jemals ein Schreiber startet, sieht ein scheinbar eingefrorenes Skript, obwohl kein Fehler vorliegt. Das ist die häufigste Quelle von Verwirrung bei Einsteigern.

Ein bewährtes Bash-Pattern, um dieses Blockieren zu umgehen, ist, die FIFO mit File-Descriptor-Umleitung sowohl zum Lesen als auch zum Schreiben zu öffnen (exec 3<>"$PIPE_PATH"). Bash öffnet den Descriptor dann im Lese-Schreib-Modus, was den Kernel dazu bringt, sofort zurückzukehren, weil aus Sicht des Kernels bereits ein Schreiber (der Prozess selbst) vorhanden ist. Dieses Muster ist besonders nützlich für Heartbeat-Skripte, die eine FIFO offen halten wollen, ohne auf einen externen Schreiber zu warten.


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

readonly PIPE_PATH="/tmp/mironsoft-heartbeat.pipe"
[[ -p "$PIPE_PATH" ]] || mkfifo "$PIPE_PATH"

# Open for BOTH read and write on fd 3 -- avoids the "wait for a writer" block
exec 3<>"$PIPE_PATH"

# Now a plain read never blocks forever, because fd 3 counts as a writer too
read -t 2 -u 3 line && echo "Received: $line" || echo "No data within timeout"

exec 3>&-  # close fd 3
rm -f "$PIPE_PATH"

5. FIFOs für Logging-Pipelines und Multiplexing nutzen

Ein produktiver Einsatzzweck für Named Pipes ist das Multiplexen mehrerer Log-Quellen in einen einzigen Verarbeitungsprozess. Statt jedes Skript einer Deployment-Pipeline seine eigene Logdatei schreiben zu lassen, schreiben alle Skripte in dieselbe FIFO, und ein zentraler Log-Collector liest daraus, versieht jede Zeile mit Zeitstempel und Quelle und leitet sie an ein Monitoring-System weiter.

Diese Architektur skaliert gut, solange klar ist, dass mehrere gleichzeitige Schreiber auf dieselbe FIFO ihre Zeilen ineinander verschachteln können, wenn eine Zeile länger als der atomare Schreibpuffer von PIPE_BUF (unter Linux 4096 Bytes) ist. Für normale Log-Zeilen ist das selten ein Problem, aber wer sehr lange, einzeilige JSON-Events schreibt, sollte das im Hinterkopf behalten oder auf ein echtes Message-Queue-System ausweichen, sobald die Zeilenlänge diese Grenze regelmäßig überschreitet.

6. FIFOs vs. Process Substitution: wann welches Werkzeug

Bash bietet mit Process Substitution (<(befehl) und >(befehl)) eine syntaktische Abkürzung, die unter der Haube in vielen Fällen exakt eine anonyme FIFO-artige Konstruktion erzeugt, auf Linux tatsächlich über /dev/fd/N. Der Unterschied: Process Substitution ist an die Lebensdauer des aufrufenden Befehls gebunden und braucht keinen expliziten mkfifo-Aufruf, während eine echte Named Pipe unabhängig von einem einzelnen Befehlsaufruf existiert und von komplett unabhängig gestarteten Prozessen genutzt werden kann.

Als Faustregel gilt: Process Substitution eignet sich, wenn Schreiber und Leser im selben Skript, in derselben Ausführung entstehen, etwa um zwei Kommando-Ausgaben mit diff <(sort a) <(sort b) zu vergleichen. Eine echte FIFO mit mkfifo ist dagegen Pflicht, wenn Schreiber und Leser in getrennten Prozessaufrufen, getrennten Cron-Jobs oder sogar getrennten Containern laufen und sich nur über einen gemeinsam bekannten Dateisystempfad finden.

7. Fehlerbehandlung und Aufräumen: trap, Race Conditions, Deadlocks vermeiden

Weil eine FIFO als Dateisystem-Eintrag bestehen bleibt, bis sie gelöscht wird, gehört trap 'rm -f "$PIPE_PATH"' EXIT in jedes Skript, das mit mkfifo arbeitet, genau wie bei temporären Dateien. Ohne diesen Trap sammeln sich verwaiste FIFOs im temporären Verzeichnis an, besonders wenn das Skript durch ein Signal oder einen Fehler abbricht, bevor es die reguläre Cleanup-Logik erreicht.

Ein Deadlock entsteht, wenn ein Skript eine Named Pipe zum Schreiben öffnet und dabei selbst der einzige Prozess ist, der sie auch lesen könnte, aber sequenziell erst nach dem Schreiben lesen will. Da das Öffnen zum Schreiben blockiert, bis ein Leser existiert, wartet das Skript auf sich selbst. Die Lösung ist entweder, den Leser in einem Hintergrundprozess (&) parallel zu starten, oder wie im vorherigen Abschnitt gezeigt, die FIFO im kombinierten Lese-Schreib-Modus zu öffnen, um das Blockieren von vornherein zu vermeiden.

8. Sicherheitsaspekte: FIFOs in gemeinsam genutzten Verzeichnissen

Wird eine FIFO in einem für mehrere Benutzer schreibbaren Verzeichnis wie /tmp angelegt, kann ein anderer lokaler Nutzer denselben Pfad vorab mit mkfifo belegen (Symlink- oder Preallocation-Angriff), sodass das eigentliche Skript unwissentlich mit den Daten eines fremden Prozesses arbeitet. Der Aufruf mkfifo schlägt zwar fehl, wenn der Pfad schon existiert, aber ein Skript, das diesen Fehler ignoriert und einfach mit der vorhandenen Datei weiterarbeitet, öffnet eine Sicherheitslücke.

Das sichere Bash-Pattern kombiniert einen mit mktemp -u erzeugten, garantiert eindeutigen Pfad mit restriktiven Rechten direkt beim Anlegen (mkfifo -m 600), sodass nur der eigene Benutzer lesen und schreiben darf. Für produktive Deployment-Skripte sollte die FIFO außerdem in einem Verzeichnis liegen, das ausschließlich dem ausführenden Service-Account gehört, nicht im global schreibbaren /tmp.

9. FIFOs im Vergleich zu anderen IPC-Mechanismen

Named Pipes sind nur eine von mehreren Möglichkeiten, wie unabhängige Prozesse unter Linux Daten austauschen. Die Wahl des richtigen Mechanismus hängt davon ab, ob Daten nur einmal fließen oder dauerhaft gespeichert werden sollen, ob mehrere Leser gleichzeitig zugreifen müssen und wie groß die zu übertragende Datenmenge ist.

Mechanismus Persistenz Mehrere Leser Typischer Einsatz
mkfifo (Named Pipe) Nein, reiner Kernel-Puffer Nein, ein aktiver Leser Zwei unabhängige Prozesse live verbinden
Zwischendatei Ja, bleibt auf Platte Ja Batch-Verarbeitung, Debugging, Audit-Trail
Process Substitution Nein Nein Schreiber/Leser im selben Skriptaufruf
Unix Domain Socket Nein Ja, mehrere Verbindungen Strukturierte Nachrichten, viele Clients
Message Queue (Redis, RabbitMQ) Optional Ja, mit Consumer-Groups Verteilte Systeme, mehrere Hosts

Mironsoft

Shell-Automatisierung, DevOps-Tooling und Deployment-Infrastruktur

Shell-Skripte, die in der Produktion zuverlässig laufen?

Wir analysieren bestehende Bash-Skripte, erkennen fragile Muster und ersetzen sie durch robuste Bash-Patterns: mit vollständiger Fehlerbehandlung, Logging und sicherer Parallelisierung für euren Deployment-Stack.

Code-Review

ShellCheck-Analyse und manuelle Prüfung auf kritische Bash-Pattern-Verstöße.

Refactoring

Fehlerbehandlung, Logging und sichere Dateioperationen nachrüsten.

CI-Integration

ShellCheck und BATS in Pipelines integrieren und Regressionstests aufbauen.

10. Zusammenfassung

Named Pipes (FIFOs) in Bash: Das Wichtigste auf einen Blick

Grundprinzip

mkfifo legt einen benannten Dateisystem-Eintrag an, der Daten nur im Kernel-Puffer hält, niemals dauerhaft auf der Platte.

Blockierendes Öffnen

Lesen blockiert ohne Schreiber und umgekehrt. exec 3<>pipe öffnet lese- und schreibbar zugleich und vermeidet Deadlocks.

Aufräumen

trap 'rm -f "$PIPE_PATH"' EXIT gehört in jedes Skript, das mkfifo verwendet, um verwaiste FIFOs zu vermeiden.

Sicherheit

mkfifo -m 600 mit mktemp-erzeugtem Pfad statt fester /tmp-Namen verhindert Preallocation-Angriffe durch andere lokale Nutzer.

11. FAQ: Named Pipes (FIFOs) in Bash: Das Wichtigste auf einen Blick

1Was ist der Unterschied zwischen einer FIFO und einer anonymen Pipe?
Eine anonyme Pipe existiert nur zwischen Prozessen derselben Shell-Pipe-Kette und hat keinen Namen im Dateisystem. Eine FIFO hat einen sichtbaren Pfad und kann von völlig unabhängigen, separat gestarteten Prozessen geöffnet werden.
2Warum bleibt mein Skript beim Lesen aus einer FIFO hängen?
Das Öffnen einer FIFO zum Lesen blockiert, bis mindestens ein Prozess sie zum Schreiben öffnet. Prüfe, ob der erwartete Schreiber überhaupt läuft, oder öffne die FIFO kombiniert mit exec 3<>pfad, um das Blockieren zu vermeiden.
3Muss ich eine FIFO nach der Nutzung löschen?
Ja. Die FIFO bleibt als Dateisystem-Eintrag bestehen, bis sie explizit mit rm gelöscht wird. trap 'rm -f pfad' EXIT sorgt dafür, dass das auch bei Fehlern oder Signalen passiert.
4Wie groß darf eine FIFO werden?
Eine FIFO speichert nichts dauerhaft, sie puffert nur im Kernel, typischerweise bis 64 KB unter Linux. Überschreitet der Schreiber das, blockiert er, bis der Leser Platz schafft.
5Kann eine FIFO mehrere gleichzeitige Leser haben?
Technisch ja, aber jede Zeile geht nur an genau einen der Leser, nicht an alle. Für echtes Broadcasting an mehrere Konsumenten ist ein Unix Domain Socket oder eine Message Queue die passendere Wahl.
6Was ist der Unterschied zu Process Substitution?
Process Substitution () erzeugt intern eine ähnliche Konstruktion, ist aber an die Lebensdauer des aufrufenden Befehls gebunden und braucht kein mkfifo. Eine echte Named Pipe existiert unabhängig davon und eignet sich für getrennte Prozessaufrufe.
7Ist das Schreiben in eine FIFO atomar?
Nur bis zur Grenze von PIPE_BUF, unter Linux 4096 Bytes. Schreibvorgänge bis zu dieser Größe verschachteln sich bei mehreren Schreibern nicht. Längere Schreibvorgänge können ineinander verschachtelt ankommen.
8Wie schütze ich eine FIFO in /tmp vor anderen Nutzern?
Mit mktemp -u einen eindeutigen Pfad erzeugen und mkfifo -m 600 direkt mit restriktiven Rechten anlegen. Noch sicherer ist ein Verzeichnis, das exklusiv dem ausführenden Service-Account gehört.
9Funktionieren FIFOs auch über Netzwerkgrenzen hinweg?
Nein. Eine Named Pipe existiert nur lokal auf einem Dateisystem. Für Prozesskommunikation über mehrere Hosts hinweg sind Sockets, Message Queues oder HTTP-APIs die richtige Wahl.
10Wann sollte ich statt einer FIFO lieber eine Zwischendatei verwenden?
Wenn die Daten auch nach Prozessende noch gebraucht werden, mehrere Leser dieselben Daten unabhängig voneinander lesen sollen, oder eine Audit-Spur gewünscht ist. Eine FIFO ist für flüchtige, einmalige Datenflüsse zwischen genau zwei Prozessen gedacht.