entr Cheat Sheet
Overview
entr is a small, focused Unix utility that watches a list of files and runs a command when any of them change. It follows the Unix philosophy of doing one thing well — it reads file paths from stdin and executes commands on modification. No configuration files, no daemons, no complex setup.
entr works on Linux, macOS, FreeBSD, and other Unix-like systems. It uses kqueue or inotify for efficient change detection without polling. Its simplicity makes it ideal for quick development feedback loops like auto-running tests, rebuilding projects, or restarting servers on file changes.
Installation
# macOS
brew install entr
# Ubuntu/Debian
sudo apt install entr
# Fedora
sudo dnf install entr
# Arch Linux
sudo pacman -S entr
# FreeBSD
pkg install entr
# From source
curl -O https://eradman.com/entrproject/code/entr-5.6.tar.gz
tar xzf entr-5.6.tar.gz
cd entr-5.6
./configure
make
sudo make install
# Verify
entr -h
Core Usage
| Flag | Description |
|---|---|
-c | Clear the screen before running the command |
-d | Track directories for new file additions |
-p | Postpone first execution until a file changes |
-r | Restart persistent processes (sends SIGTERM) |
-s | Evaluate command using $SHELL -c |
-z | Exit after command returns non-zero |
-n | Non-interactive mode (no TTY) |
Basic Patterns
# Run make when any .c file changes
ls *.c | entr make
# Run tests when source changes
find src -name '*.py' | entr pytest
# Clear screen and run
find . -name '*.go' | entr -c go test ./...
# Use shell features with -s
ls *.md | entr -s 'pandoc README.md -o README.html && echo "Done"'
# Postpone until change (don't run immediately)
find . -name '*.rs' | entr -cp cargo build
Common Workflows
Auto-Test
# Python tests
find . -name '*.py' | entr -c pytest -x
# Go tests
find . -name '*.go' | entr -c go test ./...
# Node.js tests
find src test -name '*.ts' | entr -c npx jest
# Ruby tests
find . -name '*.rb' | entr -c bundle exec rspec
# Rust tests
find src -name '*.rs' | entr -c cargo test
Auto-Build
# C/C++ project
find src -name '*.c' -o -name '*.h' | entr make
# LaTeX document
echo thesis.tex | entr pdflatex thesis.tex
# Sass/CSS
find styles -name '*.scss' | entr -s 'sass styles/main.scss dist/style.css'
# TypeScript
find src -name '*.ts' | entr -c npx tsc --noEmit
# Hugo/Jekyll static site
find content -name '*.md' | entr -s 'hugo build'
Auto-Restart Servers
# Restart Node.js server
find src -name '*.js' | entr -r node server.js
# Restart Python Flask app
find . -name '*.py' | entr -r python app.py
# Restart Go server
find . -name '*.go' | entr -r go run main.go
# Restart with cleanup
find . -name '*.py' | entr -rs 'kill $CHILD 2>/dev/null; python manage.py runserver'
Watch for New Files
# -d flag: exit when new files appear in watched dirs
# Wrap in while loop to restart with new file list
while true; do
find src -name '*.js' | entr -d npm run build
done
# Same pattern for tests
while true; do
find . -name '*.py' | entr -d pytest
done
Configuration
Filtering Files
# Exclude directories
find . -name '*.py' -not -path './venv/*' -not -path './.tox/*' | entr pytest
# Using fd (faster alternative to find)
fd -e py | entr -c pytest
# Using git ls-files (only tracked files)
git ls-files '*.ts' '*.tsx' | entr -c npm test
# Specific files only
echo "src/main.py\nsrc/config.py" | entr python src/main.py
Combining with Other Tools
# With ag/ripgrep to find files containing pattern
rg --files -g '*.py' | entr -c pytest
# With fd for globbing
fd -e go -E vendor | entr -c go test ./...
# Pipe changed filename to command (/_)
ls *.c | entr -s 'gcc /_ -o test && ./test'
# Process substitution
entr -c make < <(find src -name '*.c')
Advanced Usage
Using the /_ Placeholder
# /_ is replaced with the changed file path
ls *.py | entr -c python /_
# Compile only the changed file
ls *.c | entr -s 'gcc /_ -o ${_%.c} && echo "Built: /_"'
# Lint only the changed file
find . -name '*.py' | entr -c flake8 /_
# Format only the changed file
find . -name '*.go' | entr gofmt -w /_
Complex Workflows
# Multi-step build
find src -name '*.ts' | entr -s '
echo "=== Building ==="
npx tsc --noEmit && \
npx jest --bail && \
echo "=== All checks passed ==="
'
# Notify on completion (macOS)
find . -name '*.rs' | entr -s '
cargo test 2>&1 && \
osascript -e "display notification \"Tests passed\" with title \"Cargo\""
'
# With tmux: send keys to another pane
find . -name '*.py' | entr -s 'tmux send-keys -t 1 "pytest" Enter'
Non-Interactive Mode
# For CI/scripts (no TTY required)
find . -name '*.py' | entr -n pytest
# Exit on first failure
find . -name '*.py' | entr -zn pytest
# In Docker containers
find /app -name '*.py' | entr -rn python /app/server.py
Troubleshooting
| Issue | Solution |
|---|---|
entr: Too many files listed | Reduce file list or increase OS limits |
| New files not detected | Use -d flag in a while true loop |
| Server not restarting cleanly | Use -r flag; it sends SIGTERM to child |
| Command runs with stale data | Add -c to clear screen; check for caching |
| Not working in Docker | Use -n flag for non-interactive mode |
| Immediate exit without running | Ensure stdin has file paths; check pipes |
# Debug: check what files are being watched
find . -name '*.py' | wc -l
# Check inotify limits (Linux)
cat /proc/sys/fs/inotify/max_user_watches
# Increase limit
echo "fs.inotify.max_user_watches=524288" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
# Test with single file
echo "main.py" | entr -c python main.py