Dateisystem-Änderungen mit inotify in Bash überwachen
AI generated
$_
#!/
Bash · Linux · inotify · Automatisierung
Dateisystem-Änderungen mit inotify überwachen
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.

16 Min. Lesezeit inotifywait · inotify-tools Bash · Linux · Kernel-Events

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.

11. FAQ: inotify in Bash: Das Wichtigste auf einen Blick

1Was ist inotify in einfachen Worten?
inotify ist eine Linux-Kernel-Schnittstelle, die Programme über Änderungen an Dateien oder Verzeichnissen informiert, sobald sie passieren, statt dass das Programm regelmäßig selbst nachschaut.
2Wie installiere ich inotifywait?
Über das Paket inotify-tools, auf Debian und Ubuntu mit apt install inotify-tools, auf RHEL-basierten Systemen mit dnf install inotify-tools.
3Warum sollte ich close_write statt modify verwenden?
modify feuert bei jedem einzelnen Schreibaufruf, auch während eine Datei noch geschrieben wird. close_write feuert erst, wenn der schreibende Prozess die Datei schließt und der Inhalt konsistent ist.
4Wie überwache ich ein Verzeichnis inklusive aller Unterverzeichnisse?
Mit der Option -r registriert inotifywait automatisch einen Watch pro Unterverzeichnis. Bei sehr vielen Unterverzeichnissen kann das an das Inode-Watch-Limit stoßen.
5Was bedeutet der Fehler zum Watch-Limit?
Er zeigt, dass fs.inotify.max_user_watches erschöpft ist. Der Wert lässt sich mit sysctl -w fs.inotify.max_user_watches=524288 temporär und über /etc/sysctl.d dauerhaft erhöhen.
6Funktioniert inotify auch auf macOS?
Nein, inotify ist eine reine Linux-Funktion. Auf macOS übernimmt fswatch auf Basis der FSEvents-API eine vergleichbare Rolle mit ähnlicher Kommandozeile.
7Was ist Q_OVERFLOW und warum passiert es?
Es zeigt, dass die Kernel-Warteschlange für Ereignisse voll war, weil das Skript Events langsamer verarbeitet als sie entstehen. Dabei gehen Ereignisse unwiderruflich verloren.
8Wann ist eine einfache Polling-Schleife die bessere Wahl?
Bei seltenen, unkritischen Prüfungen, wenn keine Abhängigkeit von inotify-tools gewünscht ist oder wenn das Skript auch auf Systemen ohne inotify laufen muss.
9Wie vermeide ich mehrfaches Auslösen bei einem rsync-Lauf?
Mit Debouncing: Ereignisse in einem kurzen Zeitfenster sammeln und die eigentliche Aktion erst starten, wenn für eine definierte Zeitspanne keine weiteren Ereignisse mehr eintreffen.
10Wie betreibe ich einen inotify-Watcher dauerhaft?
Als systemd-Service mit Restart=on-failure, damit ein abstürzender oder durch Q_OVERFLOW beendeter inotifywait-Prozess automatisch neu gestartet wird.