Verzeichnisse live beobachten, ohne den Kernel mit Polling zu belasten
inotify ist eine Kernel-Schnittstelle, die Bash-Skripte sofort benachrichtigt, sobald sich eine Datei oder ein Verzeichnis ändert, statt in einer Schleife immer wieder nachzuschauen. Mit dem Kommandozeilenwerkzeug inotifywait aus dem Paket inotify-tools lassen sich Deployment-Trigger, Config-Reloads und Log-Watcher bauen, die auf echte Ereignisse reagieren statt auf eine feste Zeitspanne.
Inhaltsverzeichnis
- 1. Was inotify ist und warum es Polling überlegen ist
- 2. inotifywait installieren und einen ersten Watch aufsetzen
- 3. Verzeichnisse rekursiv überwachen und Ausgaben strukturieren
- 4. Bash-Reaktionen auf konkrete Ereignisse triggern
- 5. Grenzen: Inode-Watch-Limits und Event-Queue-Überlauf
- 6. Nur Linux: Portabilität und Container-Besonderheiten
- 7. inotify im direkten Vergleich zu einer klassischen Polling-Schleife
- 8. Debouncing, Fehlerbehandlung und dauerhafter Betrieb
- 9. Wann sich inotify lohnt und wann eine Alternative besser passt
- 10. Zusammenfassung
- 11. FAQ
1. Was inotify ist und warum es Polling überlegen ist
inotify ist eine Schnittstelle des Linux-Kernels, die Programmen erlaubt, sich für Änderungen an bestimmten Dateien oder Verzeichnissen zu registrieren und dann blockierend auf das nächste Ereignis zu warten, statt aktiv nachzufragen. Der Kernel selbst führt intern schon Buch über jede Datei-Operation, deshalb kostet die Benachrichtigung praktisch keine zusätzliche Rechenzeit: Sobald ein Prozess eine Datei anlegt, ändert, verschiebt oder löscht, weiß der Kernel das ohnehin und reicht diese Information einfach an registrierte Beobachter weiter.
Der Gegenentwurf, ein Polling-Skript, das in einer Schleife regelmäßig stat aufruft oder ein Verzeichnislisting mit einem alten Stand vergleicht, verursacht dagegen kontinuierlich Last, selbst wenn sich über Stunden nichts ändert. Außerdem erkauft sich Polling seine Einfachheit mit Latenz: Ein Ereignis wird frühestens beim nächsten Schleifendurchlauf bemerkt, bei einem Intervall von fünf Sekunden im schlechtesten Fall also fast fünf Sekunden zu spät. inotify meldet die Änderung dagegen praktisch in Echtzeit.
2. inotifywait installieren und einen ersten Watch aufsetzen
Das Kommandozeilenwerkzeug inotifywait gehört nicht zur Standardinstallation, sondern kommt aus dem Paket inotify-tools, das auf Debian und Ubuntu mit apt install inotify-tools und auf RHEL-basierten Systemen mit dnf install inotify-tools nachinstalliert wird. Danach steht ein einfaches Kommandozeilen-Frontend auf die zugrunde liegenden inotify-Syscalls bereit, ohne dass ein eigenes C-Programm gegen die Kernel-API geschrieben werden muss.
Der einfachste Aufruf inotifywait /pfad/zur/datei blockiert, bis genau ein Ereignis eintritt, gibt dann Pfad und Ereignistyp aus und beendet sich. Für ein dauerhaft laufendes Bash-Skript ist die Option -m (monitor) entscheidend: Sie hält inotifywait am Laufen und gibt jede weitere Änderung als eigene Zeile aus, sodass eine Bash-Schleife jede Zeile einzeln verarbeiten kann.
#!/usr/bin/env bash
set -euo pipefail
WATCH_DIR="/var/www/app/config"
# -m: monitor mode, keeps running instead of exiting after one event
# -e: restrict to the event types we actually care about
inotifywait -m -e modify,create,delete "$WATCH_DIR" |
while read -r directory events filename; do
echo "Change detected: $events on $directory$filename"
done
3. Verzeichnisse rekursiv überwachen und Ausgaben strukturieren
Standardmäßig beobachtet inotifywait nur die direkt angegebenen Pfade, nicht deren Unterverzeichnisse. Mit der Option -r (recursive) registriert das Werkzeug intern einen eigenen Watch für jedes Unterverzeichnis, was bei tiefen Verzeichnisbäumen mit vielen Ordnern spürbar Zeit beim Start kostet, danach aber genauso reaktionsschnell bleibt wie ein einzelner Watch.
Für maschinell auswertbare Ausgaben lohnt sich die Option --format, mit der sich Zeitstempel, Ereignistyp und Pfad in einem selbst gewählten, leicht parsbaren Format ausgeben lassen, zum Beispiel tabulatorgetrennt. Das erspart fehleranfälliges Parsen der menschenlesbaren Standardausgabe und macht das Bash-Skript robuster gegenüber Pfaden mit Leerzeichen, wenn zusätzlich mit Nullbytes getrennt wird.
#!/usr/bin/env bash
set -euo pipefail
WATCH_DIR="/var/www/app/uploads"
inotifywait -m -r -e create,moved_to \
--format '%T|%e|%w%f' --timefmt '%Y-%m-%dT%H:%M:%S' \
"$WATCH_DIR" |
while IFS='|' read -r timestamp event path; do
printf 'time=%s event=%s path=%s\n' "$timestamp" "$event" "$path"
done
4. Bash-Reaktionen auf konkrete Ereignisse triggern
Der eigentliche Nutzen entsteht erst, wenn die Schleife auf einzelne Ereignistypen unterschiedlich reagiert, statt jede Änderung gleich zu behandeln. Ein Deployment-Watcher etwa soll bei close_write, also nach einem vollständig abgeschlossenen Schreibvorgang, einen Service neu laden, aber bei einem reinen open-Ereignis nichts tun, weil die Datei zu diesem Zeitpunkt noch gar nicht fertig geschrieben ist.
Genau deshalb ist close_write für die meisten Automatisierungs-Anwendungsfälle die bessere Wahl als modify: modify feuert bei jedem einzelnen Schreibaufruf, auch mitten in einem großen Kopiervorgang, während close_write erst auslöst, wenn der schreibende Prozess die Datei tatsächlich wieder schließt und der Inhalt damit konsistent ist.
#!/usr/bin/env bash
set -euo pipefail
CONFIG_FILE="/etc/myapp/app.conf"
inotifywait -m -e close_write "$(dirname "$CONFIG_FILE")" |
while read -r directory events filename; do
if [[ "$filename" == "$(basename "$CONFIG_FILE")" ]]; then
echo "Config fully written, reloading service"
systemctl reload myapp.service
fi
done
5. Grenzen: Inode-Watch-Limits und Event-Queue-Überlauf
Jeder inotify-Watch belegt eine feste Menge Kernel-Speicher, und die Anzahl gleichzeitiger Watches pro Benutzer ist über fs.inotify.max_user_watches begrenzt, standardmäßig oft nur bei achttausend bis sechzehntausend. Wer mit -r ein sehr großes Verzeichnis wie ein komplettes node_modules mit Zehntausenden Dateien überwacht, stößt schnell an dieses Limit, und inotifywait bricht dann mit einer Fehlermeldung ab, statt nur einen Teil der Dateien zu überwachen.
Zusätzlich existiert eine Warteschlange für noch nicht abgeholte Ereignisse, begrenzt durch fs.inotify.max_queued_events. Verarbeitet die Bash-Schleife Ereignisse langsamer, als sie im Kernel entstehen, etwa weil pro Zeile eine teure Aktion ausgeführt wird, füllt sich diese Warteschlange, und bei Überlauf gehen Ereignisse unwiderruflich verloren, ohne dass das Skript automatisch davon erfährt, sofern es nicht gezielt auf das Q_OVERFLOW-Ereignis prüft.
# Show and raise the relevant kernel limits (root required to persist)
sysctl fs.inotify.max_user_watches
sysctl fs.inotify.max_user_instances
sysctl fs.inotify.max_queued_events
# Temporary increase for the current boot
sudo sysctl -w fs.inotify.max_user_watches=524288
# Persist across reboots
echo "fs.inotify.max_user_watches=524288" | sudo tee /etc/sysctl.d/99-inotify.conf
6. Nur Linux: Portabilität und Container-Besonderheiten
inotify ist eine reine Linux-Kernel-Funktion und existiert weder unter macOS noch unter den BSD-Systemen. Wer ein Bash-Skript schreibt, das auch auf macOS laufen soll, muss auf eine Alternative wie fswatch ausweichen, das intern die macOS-eigene FSEvents-API nutzt, sich in der Kommandozeile aber ähnlich verhält wie inotifywait. Ein Skript, das inotifywait fest voraussetzt, sollte deshalb beim Start prüfen, ob das Kommando überhaupt existiert, und andernfalls sauber abbrechen.
In Docker-Containern kommt eine zusätzliche Einschränkung hinzu: Wird ein Verzeichnis über ein Bind-Mount von einem Netzwerkdateisystem oder von manchen Overlay-Konfigurationen eingebunden, liefert der Host die inotify-Ereignisse teils gar nicht oder stark verzögert an den Container weiter, weil die Änderung nicht über den vom Container gesehenen Mountpoint, sondern extern auf dem Host erfolgt. Für Watcher innerhalb von Containern lohnt sich deshalb ein Testlauf gegen das tatsächliche Deployment, nicht nur gegen die lokale Entwicklungsumgebung.
7. inotify im direkten Vergleich zu einer klassischen Polling-Schleife
Eine klassische Polling-Schleife vergleicht in festen Abständen einen gespeicherten Zustand, etwa die letzte Änderungszeit einer Datei, mit dem aktuellen Zustand. Der Code dafür ist simpel und funktioniert auf jedem Unix-artigen System ohne Zusatzpaket, bezahlt diese Einfachheit aber mit ständigem CPU-Verbrauch und mit einer Reaktionszeit, die nie besser sein kann als das gewählte Schlafintervall.
Für seltene, unkritische Prüfungen, etwa ob eine Lock-Datei einmal pro Minute noch existiert, ist Polling weiterhin die pragmatischere Wahl, weil keine Abhängigkeit von inotify-tools entsteht. Sobald aber viele Ereignisse pro Sekunde erwartet werden oder eine Latenz im Millisekundenbereich wichtig ist, etwa bei einem Live-Reload während der Entwicklung, ist inotify die klar überlegene Lösung.
#!/usr/bin/env bash
set -euo pipefail
FILE="/var/lock/myapp.lock"
last_mtime=""
# Naive polling loop: wastes CPU cycles and reacts with up to 5s latency
while true; do
if [[ -f "$FILE" ]]; then
current_mtime="$(stat -c %Y "$FILE")"
if [[ "$current_mtime" != "$last_mtime" ]]; then
echo "Lock file changed"
last_mtime="$current_mtime"
fi
fi
sleep 5
done
8. Debouncing, Fehlerbehandlung und dauerhafter Betrieb
Viele reale Änderungen, etwa ein rsync-Sync oder ein Editor mit Auto-Save, erzeugen mehrere Ereignisse in schneller Folge für eine einzige logische Änderung. Ohne Debouncing löst ein naives Skript dieselbe teure Aktion, etwa einen Service-Neustart, mehrfach hintereinander aus. Eine einfache Debounce-Technik sammelt Ereignisse für ein kurzes Zeitfenster in einer Variable und führt die Aktion erst aus, wenn für eine bestimmte Zeitspanne keine weiteren Ereignisse mehr eintreffen.
Für den Dauerbetrieb gehört ein Watcher-Skript nicht in eine interaktive Terminal-Sitzung, sondern in einen systemd-Service mit Restart=on-failure, damit ein abstürzender oder durch Q_OVERFLOW beendeter inotifywait-Prozess automatisch neu gestartet wird. Zusätzlich sollte das Skript den Exit-Code von inotifywait prüfen und im Fehlerfall loggen, statt die Schleife stillschweigend zu beenden.
9. Wann sich inotify lohnt und wann eine Alternative besser passt
Die Entscheidung zwischen inotify, Polling und höherwertigen Werkzeugen hängt von Ereignisfrequenz, Portabilitätsanforderungen und der akzeptablen Reaktionszeit ab. Für lokale Linux-Automatisierung mit vielen Ereignissen pro Sekunde ist inotifywait fast immer die richtige Wahl, für portable Skripte oder seltene Prüfungen eher nicht.
| Werkzeug | Plattform | Ressourcenverbrauch | Typischer Einsatzzweck |
|---|---|---|---|
inotifywait |
Nur Linux | Sehr gering, event-basiert | Deployment-Trigger, Config-Reload, Live-Reload |
| Polling-Schleife | Überall (POSIX) | Konstant, unabhängig von Aktivität | Seltene, unkritische Prüfungen ohne Zusatzpaket |
entr |
Linux, macOS, BSD | Gering, event-basiert (inotify/kqueue) | Entwickler-Watcher, Testläufe bei Dateiänderung |
fswatch |
macOS, Linux, BSD | Gering, event-basiert | Portable Skripte über Betriebssystemgrenzen hinweg |
systemd .path-Unit |
Nur Linux (systemd) | Sehr gering, im Init-System integriert | Service-Aktivierung bei Dateiänderung ohne eigenes Skript |
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
inotify in Bash: Das Wichtigste auf einen Blick
Grundprinzip
inotify meldet Dateisystem-Ereignisse direkt aus dem Kernel, ohne dass ein Skript aktiv nachfragen muss.
Ereignistypen
close_write statt modify verwenden, damit erst nach vollständig abgeschlossenem Schreibvorgang reagiert wird.
Grenzen
fs.inotify.max_user_watches und max_queued_events begrenzen die Anzahl Watches und die Größe der Event-Warteschlange.
Portabilität
inotify existiert nur unter Linux, für macOS ist fswatch die praktische Alternative mit ähnlicher Kommandozeile.