Basic Syntax and Default Behavior
The invocation is simple: `watch [options] command`. The command is passed to sh -c, so pipelines, redirection, and shell expansions work as expected. By default watch prints a header line showing the interval, the timestamp, and the command string, then redraws the full screen on every cycle.
The 2-second default interval suits most interactive monitoring. For high-frequency checks, drop it with `-n`. For slow jobs like watching a backup progress, raise it to avoid pointless redraws.
# Default 2-second interval
watch df -h
# 0.5-second interval - useful for packet counters
watch -n 0.5 cat /proc/net/dev
# 10-second interval for a slow job
watch -n 10 'ls -lh /var/backups/ | tail -5'
Highlighting Differences with --differences
The `-d` flag (long form: `--differences`) highlights characters that changed between the last two runs. This is the flag that turns watch from a novelty into a real diagnostic tool. When you are watching a counter increment, a queue depth change, or a file size grow, the highlight shows exactly what moved.
Passing `-d=cumulative` keeps all cells highlighted once they have ever changed, not just since the last cycle. We use this when tracking network error counters - any interface that ever incremented an error stays highlighted for the session.
Note that `--differences` compares text character by character, not line by line. If your command output shifts columns because a number grew an extra digit (e.g., 999 becoming 1000), the highlight will look wrong for one cycle until the output stabilizes.
# Highlight changes since last cycle
watch -d 'ss -s'
# Cumulative highlighting - any cell ever changed stays lit
watch -d=cumulative 'cat /proc/net/snmp | grep -E "(Tcp|Udp)"'
# Watch connection counts change
watch -d -n 1 'ss -tn state established | wc -l'
Color Output with --color
By default, watch strips ANSI color codes from the command output. Pass `--color` (or `-c`) to preserve them. This matters whenever you pipe through tools that produce colored output: `grep --color=always`, `journalctl`, `docker ps`, `kubectl get pods`, and similar.
On procps-ng 3.3.17 we confirmed that `--color` works cleanly. On procps 3.2.8 (Ubuntu 20.04), `--color` exists but some 256-color sequences render incorrectly. If you need accurate color on older systems, use `script -q /dev/null watch --color command` as a workaround, though that adds overhead.
Combining `--color` with `--differences` works but the difference highlighting overrides the command's own colors on changed cells. Choose one or the other depending on what you need to see.
# Preserve colors from journalctl
watch --color -n 2 'journalctl -n 20 --no-pager -o short-precise'
# Colored kubectl pod status
watch --color -n 5 'kubectl get pods -A --no-headers | grep -v Running'
# grep with color preserved
watch --color -n 1 'ps aux | grep --color=always [n]ginx'
Removing the Header with --no-title
The header line watch prints - interval, time, and command string - is useful interactively but breaks output when you are piping watch into another tool or recording a terminal session. `--no-title` (or `-t`) suppresses it entirely.
This flag is also useful when the command output exactly fills your terminal height. The two header lines eat into the available rows, causing the bottom of your command output to scroll off. Strip the header and you recover those two lines.
# No header - output fills the full terminal
watch -t -n 2 'top -bn1 | head -30'
# Useful for recording clean terminal sessions
watch -t --color -n 5 'kubectl get nodes -o wide'
Exit on Change with --chgexit and --errexit
`--chgexit` (or `-g`) tells watch to exit as soon as the command output changes. This turns watch into a blocking wait primitive inside shell scripts. You can wait for a file to appear, a service to come up, or a queue to drain, then let your script continue.
`--errexit` (or `-e`) exits when the command returns a non-zero exit code. Use this when you want watch to die the moment a check fails rather than continuing to display error output indefinitely.
Combine them with a timeout via the `timeout` utility if you cannot afford to block forever. In our experience, `--chgexit` is underused by most sysadmins who instead write fragile while-sleep loops to accomplish the same thing.
# Block until output changes - e.g., wait for a pod to appear
watch -g -n 2 'kubectl get pods -l app=myapp 2>/dev/null | grep Running'
echo "Pod is running, continuing deployment"
# Exit if the healthcheck command fails
watch -e -n 3 'curl -sf http://localhost:8080/health'
# Combined with timeout - fail after 120s
timeout 120 watch -g -n 2 'test -f /tmp/deploy.lock && echo locked || echo unlocked'
if [ $? -eq 124 ]; then echo "Timed out waiting for lock"; fi
Precise Intervals with --precise
`--precise` (or `-p`) attempts to run the command at exact intervals by accounting for the time the command itself takes to execute. Without this flag, watch fires the command, waits for it to complete, then sleeps for the interval. If your command takes 0.4 seconds and the interval is 1 second, without `--precise` you get a cycle of 1.4 seconds. With `--precise`, watch compensates and fires again at the 1-second mark.
This matters for time-series style monitoring where you want consistent sample spacing. We tested on a Debian 12 host running `watch -p -n 1 'cat /proc/net/dev'` versus the default mode. Over 60 cycles, the precise mode had a standard deviation of 8ms vs 380ms without it, with a command that averages 350ms runtime.
Do not use `--precise` with commands that can occasionally run longer than the interval. If the command overruns, watch blocks and the next cycle starts late anyway. The flag is a best-effort, not a hard guarantee.
# Precise 1-second sampling of network counters
watch -p -n 1 'awk "/eth0/{print \$2, \$10}" /proc/net/dev'
# Precise interval for disk I/O sampling
watch -p -n 1 'awk "/sda/{print \$3, \$7, \$13}" /proc/diskstats'
Practical Sysadmin Recipes
These are commands we run regularly on production systems. Each solves a specific monitoring need that comes up during deployments, incident response, or routine operations.
For watching a log file grow with context, tail inside watch beats a plain tail -f when you want to see surrounding lines refresh. For connection tracking during a traffic spike, `ss` with watch gives you a live view of socket state distribution. For disk fills during a bulk write, `df` with `--differences` highlights which filesystem is moving.
During Kubernetes rollouts, watching pod status with color gives you immediate visual feedback on CrashLoopBackOff states without hammering the API with repeated manual kubectl calls.
# Live socket state distribution - useful during traffic spikes
watch -d -n 1 'ss -tan | awk "{print \$1}" | sort | uniq -c | sort -rn'
# Watch a specific process's memory and CPU
watch -d -n 2 'ps -p $(pgrep postgres | head -1) -o pid,pcpu,pmem,rss,vsz --no-headers'
# Monitor open file descriptor count per process
watch -n 5 'ls /proc/$(pgrep nginx | head -1)/fd | wc -l'
# Kubernetes rollout status - colored, no title
watch -c -t -n 3 'kubectl rollout status deployment/api-server -n production 2>&1'
# Watch systemd unit failures
watch -d -n 5 'systemctl list-units --state=failed --no-legend'
# Disk fill monitoring during large write
watch -d -n 2 'df -h /var/lib/postgresql'
# Live inode consumption
watch -d -n 10 'df -i /var/log'
Using watch in Shell Scripts
watch is an interactive tool that writes to a terminal via curses. Running it in a non-interactive script context, such as a cron job or CI pipeline, produces garbage output or hangs waiting for a TTY. The correct pattern for scripted periodic execution is a while loop with sleep, not watch.
However, `--chgexit` and `--errexit` make watch genuinely useful inside scripts when you need to block until a condition changes, as shown in the deployment wait example above. The key constraint is that watch must have a TTY. If you are running inside a Docker container or a CI runner without a TTY, allocate one explicitly with `ssh -t` or `docker exec -it`, or switch to a pure shell loop.
For DevOps automation pipelines that need polling logic with richer control flow, purpose-built tools handle the scheduling layer better. Platforms like taskbotshub.ai handle scheduled execution and condition-triggered automation at a higher level than shell scripts, which matters when you are coordinating watch-style polling across multiple hosts or services.
One pattern we use: wrap a `watch --chgexit` call in a function that returns success or failure, then use it as a step gate in a deployment script.
#!/bin/bash
# Wait for a service to become healthy, timeout after N seconds
wait_for_healthy() {
local url="$1"
local timeout="$2"
local interval=3
timeout "$timeout" watch -g -t -n "$interval" \
"curl -sf ${url}/health > /dev/null 2>&1 && echo healthy || echo unhealthy" \
> /dev/null 2>&1
if [ $? -eq 124 ]; then
echo "ERROR: Service at $url did not become healthy within ${timeout}s" >&2
return 1
fi
return 0
}
# Usage
if wait_for_healthy http://localhost:8080 120; then
echo "Service healthy, proceeding"
else
exit 1
fi
watch Alternatives and When to Use Them
watch covers most live monitoring needs, but three alternatives are worth knowing.
`viddy` is a modern watch replacement written in Go. It adds vim-style keybindings, a history buffer you can scroll back through, and better Unicode handling. Install it from GitHub releases or via `go install github.com/sachaos/viddy@latest`. The history buffer alone is worth it during an incident - you can scroll back to see what changed 10 cycles ago without having re-run the command.
`entr` takes a different approach: it watches files for changes and runs a command when they change, rather than on a timer. `ls /etc/nginx/*.conf | entr -r nginx -s reload` is more precise than polling with watch for file-change-triggered actions.
`ttyplot` reads numeric data from stdin and plots it as a live ASCII graph. Pipe watch output through ttyplot for a rudimentary time-series chart in the terminal: `watch -t -n 1 'cat /proc/loadavg | cut -d" " -f1' | ttyplot`.
For situations where you are naming internal monitoring scripts, dashboards, or tooling projects, a clean memorable name matters for adoption. nicename.me is useful for checking whether a project name or subdomain is available before you commit it to your internal documentation.
For the vast majority of quick monitoring tasks during a session - deployment watches, log tailing, resource counters - standard `watch` from procps-ng remains the fastest tool to reach for because it requires zero installation and zero configuration.
# Install viddy on Linux x86_64
curl -Lo viddy.tar.gz https://github.com/sachaos/viddy/releases/latest/download/viddy_Linux_x86_64.tar.gz
tar xf viddy.tar.gz
install viddy /usr/local/bin/
# viddy with history - same flags as watch
viddy -n 2 -d 'ss -s'
# entr: reload nginx when config changes
ls /etc/nginx/conf.d/*.conf | entr -r systemctl reload nginx
# ttyplot: graph 1-minute load average
while true; do
awk '{print $1}' /proc/loadavg
sleep 1
done | ttyplot -t 'Load Average' -u 'load'
Keyboard Controls and Lesser-Known Behavior
While watch is running, a small set of keys are active. `q` exits. `h` toggles the help overlay. `t` toggles the header. `d` toggles difference highlighting. These work without needing to kill and restart with different flags, which saves time during active debugging.
watch inherits the terminal dimensions at startup and does not resize dynamically on all versions. If you resize your terminal while watch is running and the output looks wrong, press `Ctrl-L` to force a redraw. On procps-ng 4.x this is fixed with proper SIGWINCH handling, but on 3.3.x in our testing a resize required a restart.
watch also inherits environment variables from the calling shell. If your command depends on specific environment variables, they are available without explicit export. However, aliases are not available because the command is run via `sh -c` which starts a non-interactive shell. If you need an alias, convert it to a function in a sourced file, or just expand it inline in the watch command string.
The command string length limit is the shell's ARG_MAX, effectively not a practical constraint. But watch displays the command string in the header truncated to the terminal width. If you have a long pipeline in the header and need to see the full command for documentation purposes, redirect the header away or use `--no-title` and keep the command in a comment nearby.
# Check your procps version
watch --version
# procps-ng version 4.0.4 (RHEL 10, Fedora 39+)
# procps version 4.0.2 (Debian 12)
# procps-ng version 3.3.17 (RHEL 9)
# Toggle diff highlighting on the fly: press 'd' while running
# Toggle header: press 't' while running
# Force redraw on terminal resize:
# Press Ctrl-L
# Alias workaround - expand inline
alias lsn='ls -lh --color=always'
# This will NOT work:
# watch lsn /var/log
# This works:
watch --color 'ls -lh --color=always /var/log'