Skip to content
shellmap

Run a command when a file changes

Auto-execute a build / test / reload step the moment a watched file is saved — the dev-loop primitive behind every "live-reload" tool, without committing to a specific framework.

How to run a command when a file changes in each shell

Bashunix
ls *.py | entr -c -r python app.py

`entr` reads filenames from STDIN, runs the command, and re-runs it on any change. `-c` clears the terminal each run; `-r` RESTARTS the running command (kills + relaunches — perfect for dev servers); `-p` waits for change before first run (skip-initial). Install: `apt install entr` / `brew install entr` / `apk add entr`. Tiny C program — works on Linux, macOS, BSD. The dir-watch variant: `find . -name "*.py" | entr -r python app.py`; rebuild list on new files: `while sleep 1; do find . -name "*.py"; done | entr -r …`.

Zshunix
ls *.py | entr -c -r python app.py
Fishunix
ls *.py | entr -c -r python app.py
PowerShellwindows
$f = New-Object IO.FileSystemWatcher (Resolve-Path .), '*.py'; $f.IncludeSubdirectories = $true; $f.EnableRaisingEvents = $true; Register-ObjectEvent $f Changed -Action { Start-Process python app.py }

`IO.FileSystemWatcher` is the .NET API; `Register-ObjectEvent` ties a script-block to the event. Runs on a BACKGROUND runspace — your foreground prompt stays interactive. Stop with `Get-EventSubscriber | Unregister-Event`. Caveats: editors that save by "write-temp + rename" (vim, IntelliJ) fire BOTH Created AND Renamed events — debounce with a 200ms timer or you'll re-run twice per save. For simpler ad-hoc: install `entr` via WSL/Cygwin, or use `nodemon` (npm) if you have Node.

cmd.exewindows
powershell -NoProfile -Command "$f = New-Object IO.FileSystemWatcher '.\\','*.py'; $f.EnableRaisingEvents = $true; Register-ObjectEvent $f Changed -Action { python app.py }; while($true){Start-Sleep 1}"

cmd has NO file-watch primitive. Shell to pwsh (above) for proper event-driven; `while($true){Start-Sleep 1}` keeps the host alive so the event loop fires. Polling fallback in pure cmd: `:loop & python app.py & timeout /t 5 & goto loop` — re-runs every 5s regardless of change. Inefficient but works without dependencies.

Equivalents listed for Bash, Zsh, Fish, PowerShell, cmd.exe.

Gotchas & notes

  • **`entr` is the most portable answer** — single C binary, no daemon, no config file. Pattern: pipe a file LIST into entr; entr watches those exact files. On macOS uses kqueue; on Linux uses inotify; on BSD uses kqueue. Limitations: **does not watch for NEW files** (the file list is fixed at start) — works for "watch this set of source files I already have". For "watch the whole directory including new files": pair with a re-scan loop: `while sleep 1; do find src -name "*.py"; done | entr -r make test` — re-emits the list every second so entr picks up additions. Each entr invocation can have ONE child process; for multiple parallel watchers, run multiple `entr` pipelines.
  • **`inotifywait` (Linux only) is the lowest-level option**: `inotifywait -m -e modify -r src/` watches `src/` recursively, prints one line per modify event. Wrap in a loop: `inotifywait -m -e modify -r src/ | while read f; do make test; done`. **Debounce** matters: editors often modify a file 2-3 times in rapid succession during save → triggers `make test` multiple times. Solution: `inotifywait` + a coalescing read with timeout: `while read -t 0.5 f; do …; done` (the `read -t 0.5` waits 500ms for more events; if none, runs the action — events within 500ms are coalesced). Install: `apt install inotify-tools` / `apk add inotify-tools`. Not on macOS (no inotify); use `fswatch` there.
  • **`fswatch` (macOS, also works on Linux/BSD) abstracts the OS-level APIs**: uses FSEvents on macOS, inotify on Linux, kqueue on BSD. `fswatch -o src/ | xargs -n1 -I{} make test`. The `-o` emits a single event per BATCH (not per file) — built-in debouncing. `-l 0.5` tunes the batch window. `xargs -n1 -I{}` consumes one event per iteration. Install: `brew install fswatch` / `apt install fswatch`. For "watch + execute" in a single invocation: `fswatch -o src/ | (xargs -n1 -I{} bash -c "make test")`. **Race condition** to know: if `make test` takes 30s and the user saves 3 times during that window, fswatch buffers them all and re-runs 3 times after the first finishes — sometimes desired, sometimes a thundering-herd problem. For "always run the LATEST save once", use `entr -r` (which kills the previous run).
  • **Windows `Register-ObjectEvent` FileSystemWatcher — the pwsh idiom**: `$fsw = New-Object IO.FileSystemWatcher path, filter; $fsw.IncludeSubdirectories = $true; $fsw.EnableRaisingEvents = $true; Register-ObjectEvent $fsw "Changed" -Action { … }`. Events fire on a BACKGROUND PowerShell runspace — variables from the foreground are NOT in scope; use `$Event.SourceEventArgs.FullPath` to access the changed path. **Buffer overflow** under high churn: default 8KB buffer; high event rate → `OnError` "Too many changes" event; bump with `$fsw.InternalBufferSize = 64KB`. Unregister at session end: `Get-EventSubscriber | Unregister-Event`. For long-running scripts: wrap in try/finally with cleanup so dangling subscribers don't leak across pwsh sessions.
  • **Polling fallback** when no event-watcher is available: `while true; do find src -newer .last_run -type f; touch .last_run; sleep 2; done | while read f; do make test; done` — checks every 2s for files modified since last run, touches sentinel, runs build. **Quick to implement, expensive at scale** (re-stats every file every 2s). On WSL2 `/mnt/c/...` paths (Windows-side files), inotify DOES NOT WORK — DrvFs doesn't emit events. Forced to either: (a) move files into WSL filesystem (`/home/user/src/`), (b) poll, or (c) run the watcher from the Windows side (pwsh FileSystemWatcher) and trigger WSL build via `wsl.exe make test`. Hot-reload performance on `/mnt/c/...` is famously bad; the move-files-into-WSL workaround is the only clean fix.

Related commands

Related tasks