2026-04-26
Every few years someone builds a new file-watching tool with a config file, a daemon, a plugin system, and 47 npm dependencies. Meanwhile, entr has been quietly doing the job since 2012 with zero configuration, zero dependencies, and a manpage you can read in two minutes.
entr takes a list of filenames on stdin and runs a command when any of them change. That's it. That's the whole tool.
# Rerun tests when any Python file changes
find . -name '*.py' | entr pytest
# Rebuild when source changes — -c clears the screen first
find src -name '*.c' | entr -c make
# Restart a server on change — -r sends SIGTERM then reruns
find . -name '*.go' | entr -r go run ./cmd/server
The -r flag is where entr earns its keep. It manages the child process lifecycle — kills the old one, waits for it to die, starts the new one. No PID files, no orphaned processes, no "port already in use" at 2 AM.
The /_ placeholder. This is the trick most people miss. Use /_ in your command and it gets replaced with the single file that changed:
# Only recompile the file that changed
find . -name '*.tex' | entr pdflatex /_
# Lint just the modified file, not the whole project
git diff --name-only | entr flake8 /_
This turns a 30-second full-project lint into a sub-second single-file check. Your feedback loop shrinks by an order of magnitude.
Pairing with git ls-files. Forget find. Your repo already knows which files matter:
# Watch only tracked files
git ls-files | entr make test
# Watch tracked files plus untracked new ones with -d
# -d exits when a new file is added to a directory
while true; do git ls-files | entr -d make test; done
The -d flag handles the one edge case that bites every file watcher: new files. When a new file appears in a watched directory, entr exits with a special status code. Wrap it in a while true loop and it re-scans, picking up the new file. Crude? Yes. Reliable? Absolutely.
Why not inotifywait? You can build this with raw inotifywait, but you'll spend an afternoon handling race conditions, debouncing rapid saves, and filtering editor temp files. entr handles all of this. It uses kqueue on BSDs and inotify on Linux, debounces by default, and ignores files that disappear momentarily (looking at you, Vim).
Why not nodemon/watchexec/fswatch? They're fine tools. But entr composes. It doesn't need glob patterns or config files because it reads filenames from stdin — which means every file-listing tool in Unix becomes its configuration language. find, fd, git ls-files, rg --files, even ls. You already know how to use all of them.
Install it everywhere:
# Debian/Ubuntu
apt install entr
# macOS
brew install entr
# From source (it's tiny — ~500 lines of C)
./configure && make install
One real-world pattern I use daily — live-previewing markdown as I write:
ls draft.md | entr -c pandoc /_ -o /tmp/preview.html
No electron app. No VS Code extension. No webpack dev server. Just a pipe, a command, and a tool that does one thing well.
entr turns any list of filenames piped to stdin into a live-reloading development environment — no config, no daemon, no dependencies, just Unix composition at its finest.
