inotifywait: Watching Filesystem Events

inotifywait: Watching Filesystem Events

What is inotifywait?

Conclusion: inotifywait uses the Linux inotify kernel feature to detect file create/modify/delete events without polling, so you can trigger automation the moment a file changes.

  • Detect filesystem events in real time with inotifywait
  • Combine it with a while read loop to build a change-detect → auto-process pattern
  • Avoid the two pitfalls: close_write and max_user_watches

Quick Summary

  • Wait once → inotifywait path
  • Monitor continuously and process events → inotifywait -m -e close_write path
  • To catch "save finished," use close_write, not modify

Assumptions (environment)

  • OS: Linux (Ubuntu / Debian / RHEL family), inotify on kernel 2.6.13+
  • The inotify-tools package is required (see below)
  • Local filesystem assumed (some network filesystems like NFS cannot report events)

Why use inotifywait? (vs polling)

Conclusion: Polling with while sleep 1; do ...; done wastes CPU and reacts late. inotifywait lets the kernel push events, so it stays idle-cheap and reacts instantly.

The naive way to detect changes is to repeatedly run ls or stat — polling. That has downsides:

  • A short interval wastes CPU and I/O
  • A long interval delays detection
  • Scan cost grows with the number of watched files

inotifywait registers events with the kernel's inotify subsystem and only wakes up when a change happens. Idle load is near zero and the reaction is immediate. Where the watch command re-renders on a fixed interval (polling), inotifywait is event-driven.

How do you install inotifywait?

Conclusion: inotifywait is not part of coreutils — it ships in the inotify-tools package. Install it with apt on Debian-based systems or dnf on RHEL-based ones.

# Ubuntu / Debian
$ sudo apt install inotify-tools

# RHEL / Rocky / AlmaLinux (may require EPEL)
$ sudo dnf install inotify-tools

# Fedora
$ sudo dnf install inotify-tools

Verify the install:

$ inotifywait --help | head -n 1
inotifywait 3.22.6.0

The bundled inotifywatch tool aggregates event statistics ("which files were accessed how often"). This article focuses on inotifywait, which streams events one by one for processing.

Basic usage: watching for file changes

Conclusion: Pass a path and inotifywait blocks until one event occurs, prints it, and exits. Add -m (monitor) to keep watching continuously.

Wait once (one-shot)

$ inotifywait /tmp/watchdir

Touch a file in /tmp/watchdir from another terminal and it prints one event, then exits:

Setting up watches.
Watches established.
/tmp/watchdir/ MODIFY test.txt

The output order is watched-path event-name filename. By default it watches all event types and exits on the first one.

Monitor continuously (-m)

Exiting every time is useless for automation. With -m (--monitor) it never exits and keeps emitting events:

$ inotifywait -m /tmp/watchdir
Setting up watches.
Watches established.
/tmp/watchdir/ OPEN test.txt
/tmp/watchdir/ MODIFY test.txt
/tmp/watchdir/ CLOSE_WRITE,CLOSE test.txt

Startup messages like Setting up watches. go to standard error. Suppress them with -q (quiet) when scripting.

Which events should you watch? (choosing -e)

Conclusion: Use -e to narrow the events. To catch "file save complete," use close_write, not modifymodify fires on every write.

Without -e (--event), every event type is in scope, which is noisy. Common events:

Event Fires when
create A file / directory was created
modify Contents were written (fires multiple times)
close_write A file opened for writing was closed
delete A file / directory was deleted
moved_to A file was moved / renamed into this directory
moved_from A file was moved / renamed out of this directory
attrib Permissions, owner, or timestamps changed

Specify several events by repeating -e or comma-separating them.

# Watch only create, write-complete, and delete
$ inotifywait -m -e create -e close_write -e delete /tmp/watchdir

Why close_write instead of modify

Editors and programs often write multiple times for a single save, and modify fires each time. To catch the moment a save finishes exactly once, use close_write, which means the file was closed. Miss this and you get "the same file processed over and over."

Formatting output: --format and --timefmt

Conclusion: --format reshapes the output. The basics are %w watched path, %f filename, %e event name, and %T timestamp (used with --timefmt).

The default output is space-separated and awkward to parse. Use --format to produce something script-friendly.

$ inotifywait -m --timefmt '%F %T' --format '%T | %e | %w%f' \
    -e close_write /tmp/watchdir
2026-06-05 21:30:11 | CLOSE_WRITE,CLOSE | /tmp/watchdir/report.csv

Key format specifiers:

  • %w … the watched path (the directory being watched)
  • %f … the filename the event applies to (when watching a directory)
  • %e … the events that occurred (comma-separated)
  • %T … timestamp, formatted by the --timefmt strftime string

Joining %w%f gives the full path. To change the separator, use %Xe, where X is the separator character.

Watching directories recursively (-r and watch limits)

Conclusion: -r watches subdirectories recursively, but each directory consumes one watch, and hitting the fs.inotify.max_user_watches limit makes it fail.

$ inotifywait -m -r -e close_write /var/www

Watching a huge tree with -r can produce this error:

Failed to watch /var/www; upper limit on inotify watches reached!
Please increase the amount of inotify watches allowed per user via `/proc/sys/fs/inotify/max_user_watches'.

Check the current limit:

$ cat /proc/sys/fs/inotify/max_user_watches
8192

Raise it temporarily (resets on reboot):

$ sudo sysctl fs.inotify.max_user_watches=524288

Make it persistent with a config file:

$ echo 'fs.inotify.max_user_watches=524288' | sudo tee /etc/sysctl.d/90-inotify.conf
$ sudo sysctl --system

Another recursive-watch caveat

inotify sets watches per directory. A deep subdirectory created after monitoring starts can miss events during the brief window before its watch is added (a race). For trees where subdirectories appear frequently, design with this possible miss in mind.

Practical: detect changes and process automatically

Conclusion: Pipe inotifywait -m into a while read loop and process each file. Passing only the values you need via --format keeps parsing stable.

Process a CSV every time one is fully written into a watched directory:

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

WATCH_DIR=/var/spool/incoming

inotifywait -m -q \
    --format '%w%f' \
    -e close_write \
    "$WATCH_DIR" |
while read -r filepath; do
    case "$filepath" in
        *.csv)
            echo "Processing: $filepath"
            # Run the real work here (ingest, transform, notify, etc.)
            ;;
        *)
            echo "Skipping: $filepath"
            ;;
    esac
done

Key points:

  • -q suppresses startup logs; --format '%w%f' passes only the full path
  • Narrowing to close_write makes "save complete" the only trigger
  • read -r keeps backslashes literal

Prevent concurrent runs

If you launch this monitor from cron or systemd, two instances could process the same file twice. Pair it with flock file locking to keep it to a single instance.

Watch out for editors' "atomic save"

Editors like vim may save by "writing a temp file, then renaming it over the original." In that case the change shows up as moved_to or create, not close_write. To reliably catch editor saves, watch the directory for both -e close_write -e moved_to.

inotifywait FAQ

Conclusion: Exit status is 0 on an event, 1 on error, and 2 on a -t timeout. Some network filesystems like NFS cannot detect changes made by other hosts.

Q. Exit if no event happens within a time limit

Use -t (--timeout) with a number of seconds. On timeout, the exit status is 2.

$ inotifywait -t 30 -e close_write /tmp/watchdir

Q. Exclude certain paths from watching

Pass an extended regular expression to --exclude (case-sensitive) or --excludei (case-insensitive).

$ inotifywait -m -r --exclude '\.git/' -e close_write /repo

Q. Can it watch files on NFS?

inotify only reports changes observed by the local kernel. On network filesystems like NFS, where another host writes the files, those changes may not be detected. Consider polling or another mechanism for shared storage.

Next Reading