Skip to main content

Linux command: watch

23 August 2026

You are waiting for something. A file to finish copying, a queue to drain, a certificate to renew, a container to come up. So you press the up arrow and hit enter, and then again, and again. There is a command that does that for you, it has been in every Linux install for thirty years, and almost everybody who uses it knows exactly one of its options. watch repays about ten minutes of attention with a tool you will reach for weekly.

1. The Basics

watch runs a command over and over and shows you the output on a cleared screen. That is all it does. Everything interesting comes from the details of how it runs the command, and from the fact that it is not the tool most people assume it is.

1.1 The Simplest Possible Use

Put watch in front of any command:

$ watch date

The screen clears and fills with two things, a header and the output:

Every 2.0s: date                              xps: Sun Aug 23 21:13:02 2026

Sun Aug 23 09:13:02 PM CEST 2026

Every two seconds the command runs again and the screen is redrawn. Ctrl-C stops it. There is no other way out, which surprises people who expect q to work: watch is not a pager.

1.2 Reading the Header

The header is four pieces of information and it is worth knowing them, because two of them answer the questions you are most likely to ask while staring at the screen.

PartMeans
Every 2.0s The interval. Confirms that -n was understood, which matters more than you would think.
date The command as watch received it, after your shell finished with it. Section 5.1 is entirely about this.
xps The hostname. Useful when three terminals are open on three servers.
Sun Aug 23 21:13:02 2026 When this refresh happened. If it stops advancing, the command is hanging.

That last point is the one to remember. A frozen clock in the header means your command has not returned, not that watch has died.

1.3 The Interval Is a Gap, Not a Schedule

Almost everyone reads -n 5 as "run this every five seconds". It does not mean that. It means "wait five seconds after the command finishes, then run it again". If the command is slow, the cycle is slower than you asked for.

Here is a command that takes two seconds, with the interval set to half a second, logging the moment each run starts:

$ watch -n 0.5 'date +%s.%N >> t.log; sleep 2'

# gaps between runs, in seconds:
2.5   2.5   2.5

Not 0.5. Not 2.0. The interval plus the runtime, every time. This is why a watch on something slow, a database query, a du over a large tree, a command that does a DNS lookup, drifts steadily away from the rhythm you thought you set. Section 6.3 shows the option that fixes it, and section 7.1 shows the case where even that cannot help.

The right mental model: watch is a loop that sleeps between runs, not a scheduler that fires at intervals. Every quirk in this article follows from that sentence and from the shell in section 5.1.

Back to top

2. Where the Name Comes From

For once there is no etymology puzzle. It is called watch because you watch it. No acronym, no missing vowel, no 1970s teletype constraint.

The interesting question is the opposite one: what the name makes people expect. "Watch" in most modern tooling means watch for changes and react, the way webpack --watch or cargo watch or a test runner in watch mode does. Those tools sit idle until a file changes, then do something.

Linux watch does not do that. It has no idea what a file is. It runs a command on a timer and paints the result, and if nothing has changed it paints the same thing again. The distinction matters enough that section 7.4 is about the tools that do the other job, because reaching for watch when you wanted entr is a genuinely common mistake.

Two more names worth having straight, because error messages and package searches use them:

procps      the original package of /proc tools: ps, top, free, uptime, watch
procps-ng   the "next generation" fork from 2011, which is what you now run
watch(1)    the manual page; note the 1, because there is no watch(8)

If you search the internet for "watch command" you will also find the shell builtin wait, the systemd concept of a path unit, and a Bash pattern using while true; do ... sleep; done. Section 9.1 compares that last one directly, because it is the thing watch actually replaces.

Back to top

3. A Short History

watch is older than most of the tools people compare it to, and it has been rewritten more than once. The copyright file on any Debian or Ubuntu machine still carries the original name:

$ grep -A3 "Tony Rems" /usr/share/doc/procps/copyright
Copyright: 1998-2004 Albert Cahalan
           1991 Tony Rems <This email address is being protected from spambots. You need JavaScript enabled to view it.>
           1993 Larry Greenfield
           1996 Charles Blake
EraMilestone
1991 Tony Rems writes the original watch, with later fixes from Francois Pinard.
1999 Mike Coleman reworks it and adds most of the options people use today, including -d.
2008 Morty Abzug adds --beep, --exec and the error handling behind --errexit.
2011 The procps-ng fork takes over maintenance after the original procps stalls. Every mainstream distribution follows it.
2022 procps-ng 4.0.0 adds --equexit: exit when the output stops changing, the mirror image of --chgexit.
2023 procps-ng 4.0.4 turns colour interpretation on by default, and adds -C to switch it off. Section 7.6 explains why that broke some old commands.

Two things follow from that timeline and both matter in practice.

First, watch is not in POSIX and never has been. It is a Linux convenience from the procps family, so it is not guaranteed on a BSD, on macOS (where you install it from Homebrew), or inside a minimal container image. A script that depends on it is a script that will fail somewhere, and section 6.4 covers what to write instead.

Second, the options are not uniform across machines. The newest ones (-q, -r, -w) are absent on older servers, so check before you rely on them:

$ watch --version
watch from procps-ng 4.0.4
Back to top

4. Simple Use Cases

4.1 Setting the Interval

-n (short for interval, despite the letter) takes seconds, and it accepts fractions:

$ watch -n 5 systemctl status nginx      # every 5 seconds
$ watch -n 0.5 ls -l /var/spool/mail     # twice a second
$ watch -n 60 df -h                      # once a minute

There is a floor at 0.1 seconds, and watch does not warn you when it clamps. It just quietly uses the minimum, which you can see in the header:

$ watch -n 0.01 true
Every 0.1s: true                              xps: Sun Aug 23 21:16:24 2026

Two smaller details that save a puzzled minute. A comma works as well as a full stop, for the benefit of European locales:

$ watch -n 0,5 true
Every 0.5s: true                              xps: Sun Aug 23 21:16:24 2026

And if you always want a different default, set it once in your shell profile instead of typing -n forever:

$ export WATCH_INTERVAL=5
$ watch true
Every 5.0s: true                              xps: Sun Aug 23 21:16:25 2026

Garbage is rejected rather than ignored, which is the behaviour you want:

$ watch -n abc true
watch: failed to parse argument: 'abc': Invalid argument

4.2 Highlighting What Changed

-d (short for differences) is the option that turns watch from a refreshing screen into something you can actually read. It shows changed characters in reverse video, so your eye goes straight to the number that moved:

$ watch -d free -h
$ watch -d 'ls -l /var/log/nginx'
$ watch -d -n 1 'ss -tn state established | wc -l'

By default the highlight shows what changed since the previous refresh, so it appears for one cycle and then fades. If you want a running record of everything that has moved since you started watching, ask for the permanent form:

$ watch -d=permanent -n 5 df -h        # every cell that has EVER changed stays lit

That variant is the better one for walking away from the terminal. Come back in ten minutes and the screen tells you which filesystems moved while you were gone, rather than only what happened in the last five seconds.

There is one thing that defeats -d completely, and it is worth knowing before you trust the highlighting. watch compares the screen position by position, not line by line. If the rows move, every character after the move counts as changed:

# three rows, one value changes in place
alpha 1        alpha 1
beta  2   ->   beta  9        highlighted spans: 1
gamma 3        gamma 3

# the same three rows, same values, different order
alpha 1        gamma 3
beta  2   ->   alpha 1        highlighted spans: 4
gamma 3        beta  2

Nothing changed in the second case, and -d lit up four times as much. That is exactly what happens with watch -d 'ps aux --sort=-%cpu': processes trade places constantly, the whole screen flashes, and the highlighting tells you nothing. Sort by something stable instead, by name or by PID, and let the numbers move within fixed rows.

4.3 Turning Off the Header

-t (short for no-title) removes the header line and the blank line under it, giving you two more rows of output and a cleaner screenshot:

$ watch -t -n 1 uptime

Use it when the output is tall and you need the space. Keep the header when you are debugging, because the timestamp is how you tell a hung command from an idle one.

4.4 Colour

Many commands print colour when they think a terminal is watching. watch used to strip those codes unless you passed -c, and since procps-ng 4.0.4 it interprets them by default:

$ watch -c ...      # interpret ANSI colour (now the default)
$ watch -C ...      # do NOT interpret it, print the codes as text

Most tools still need telling that colour is wanted, because they see a pipe rather than a terminal and disable it themselves:

$ watch 'ls --color=always -l'
$ watch 'grep --color=always ERROR /var/log/app.log'
$ watch 'systemctl --no-pager status nginx'

That --no-pager is the same idea in a different coat. Anything that would normally open less must be told not to, or watch shows you a pager waiting for input that will never come.

Back to top

5. Moderate Use Cases

5.1 The Quoting Rule, Which Is the Whole Game

More watch confusion comes from this than from everything else combined. Your shell processes the command line before watch ever sees it. Then watch hands what is left to sh -c, a second shell, for every single refresh.

Two shells, and the difference is visible. Here is the same command written three ways, with the outer shell's own PID being 468449:

$ echo $$
468449

$ watch -t echo $$
468449                             # frozen: YOUR shell expanded it, once

$ watch -t echo '$$'
468474                             # a new number every refresh: watch's sh did it
468476
468478
468480

$ watch -t -x echo '$$'
$$                                 # no shell at all, so nothing expanded it

Read those three results until they make sense, because they are the whole model. Unquoted, your shell substitutes the value once and watch repeats a fixed string forever. Quoted, the substitution happens inside watch, fresh each cycle. With -x, there is no shell to substitute anything.

The practical rule is short: if the command contains anything the shell cares about, quote the whole thing. That means $, |, >, &&, *, backticks and semicolons.

5.2 Pipes Belong Inside the Quotes

This is the same rule, but it deserves its own demonstration because the failure is silent rather than noisy:

$ watch ls /etc | head -3
# the whole of /etc fills the screen.
# the pipe applied to WATCH's output, not to ls.

$ watch 'ls /etc | head -3'
adduser.conf
alsa
alternatives                       # correct

Nothing errors in the first case. Your shell built a pipeline out of watch itself, watch took over the screen anyway, and head sat there with nothing useful to do. You get a plausible-looking screen that is not what you asked for, which is the worst kind of wrong.

The same trap applies to redirection and to chaining:

$ watch 'systemctl is-active nginx && curl -s -o /dev/null -w "%{http_code}" localhost'
$ watch 'tail -5 /var/log/syslog 2>&1'

5.3 When to Use -x

-x (short for exec) skips the sh -c entirely and runs your command directly. Use it when the extra shell is causing you grief rather than helping:

$ watch -x kubectl get pods -o wide
$ watch -x php artisan queue:monitor default

The trade is that you lose every shell feature. Pipes, redirects, variables and globs stop being special and become ordinary arguments:

$ watch -x ls /etc '|' head
ls: cannot access '|': No such file or directory
ls: cannot access 'head': No such file or directory

If you need both a pipeline and -x, you have to supply the shell yourself, at which point you may as well not use -x:

$ watch -x sh -c 'echo ok | tr a-z A-Z'
OK

In practice -x earns its place with commands carrying awkward arguments, JSON, braces, quotes inside quotes, where getting the nesting right for sh -c is more trouble than the shell is worth.

5.4 You Only Get the First Screenful

watch shows what fits and discards the rest. It does not scroll, it does not page, and there is no key to see more. A hundred-line file in a ten-row terminal shows eight lines:

$ seq 1 100 > hundred.txt
$ watch cat hundred.txt            # in a 10-row terminal

Every 2.0s: cat hundred.txt                   xps: Sun Aug 23 21:17:11 2026

1
2
3
4
5
6
7
8                                  # lines 9 to 100 are simply gone

This is not a bug to work around, it is a constraint to design for. Make the command produce a summary rather than a dump:

$ watch 'tail -20 /var/log/nginx/error.log'
$ watch 'ls -1 /var/spool/queue | wc -l'
$ watch 'df -h | grep -E "^/dev|Filesystem"'
$ watch 'ps aux --sort=-%mem | head -12'

The last one is the pattern worth internalising: sort by what you care about, then take the top few. A screen that always fits is a screen you can read at a glance, which is the entire point of the tool.

5.5 Long Lines

By default a line longer than the terminal wraps onto the next row, which eats your limited vertical space and makes a table unreadable. -w truncates instead:

$ watch -w 'ps aux --sort=-%cpu | head -15'

For anything with columns, truncating is almost always the better choice: a cut-off table still lines up, a wrapped one does not.

5.6 Watching the Kernel's Own Files

watch and the /proc filesystem were made for each other, and the reason is a detail most people never notice. The files in /proc are not files. They have no contents until you read them, and the kernel generates the answer at that moment:

$ ls -l /proc/meminfo /proc/loadavg
0 /proc/loadavg
0 /proc/meminfo                    # zero bytes, both of them

$ cat /proc/uptime
215685.54 3283350.21
$ cat /proc/uptime
215686.54 3283365.06               # a second later, a fresh answer

Every refresh is therefore a genuinely new reading of live kernel state, taken with nothing more exotic than cat. That makes a large amount of what the kernel knows available to a one-line command:

$ watch -d 'cat /proc/loadavg'
$ watch -d 'cat /proc/meminfo'
$ watch -d 'grep -E "^(procs_running|procs_blocked)" /proc/stat'
$ watch -n 2 'cat /proc/mdstat'              # a RAID rebuild, the classic use
$ watch -d 'cat /proc/net/dev'

/sys works the same way for hardware and driver state, usually one value per file:

$ watch -n 5 'cat /sys/class/thermal/thermal_zone0/temp'
20000                              # millidegrees, so 20 C

$ watch -n 1 'cat /sys/class/net/eth0/statistics/rx_bytes'

That last one comes with the single most important limitation in this whole article, and it catches people who are otherwise using watch well. rx_bytes is a total, not a rate. It only ever climbs, and watching it climb tells you nothing about how fast traffic is flowing.

To get a rate you need two readings and the time between them, and watch keeps no memory of the previous refresh. It cannot subtract. Anything of the form "per second" is therefore outside what it can do, no matter how short the interval:

$ vmstat 1                         # rates, with scrolling history
$ iostat -x 2                      # per-device I/O rates
$ sar -n DEV 1                     # network rates, and it keeps the history
$ watch -n 1 'cat /sys/class/net/eth0/statistics/rx_bytes'   # a climbing total

The general shape of the rule: watch is excellent at showing you a value, and incapable of showing you a change over time beyond the one-cycle hint that -d gives. Section 7.5 is about the consequences of that.

Back to top

6. Advanced Use Cases

6.1 Stop Watching When Something Happens

This is the feature that turns watch from a display into a tool you can build on, and almost nobody knows it exists. -g (short for chgexit) exits as soon as the output differs from the previous run:

$ watch -g -n 5 'systemctl is-active nginx'
# ... sits there while the answer stays "active" ...
# ... the moment it becomes "inactive", watch exits

That means you can wait for a condition without writing a polling loop. The command becomes a blocking operation you can put in a script:

$ watch -g -n 10 'ls /var/spool/upload | wc -l' && echo "the queue changed"
$ watch -g -n 2 'curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/'

-q (short for equexit) is the mirror image: exit when the output has stopped changing for a given number of cycles. That is the "wait until it settles" case, which comes up constantly with copies, deployments and imports:

$ watch -q 5 -n 2 'du -s /var/backups'
# exits once the size has been identical for 5 consecutive checks

The two together cover most of what people write shell loops for. -g is "tell me when it moves", -q is "tell me when it stops".

6.2 Stop Watching When Something Breaks

-e (short for errexit) freezes the display when the command exits non-zero, so the failure stays on screen instead of being wiped by the next refresh two seconds later. It then waits for a key press before quitting:

$ watch -e -n 5 'curl -sf https://example.test/health'

Without -e, a transient failure flashes up and vanishes and you spend ten minutes wondering whether you imagined it. With it, the screen holds still at exactly the moment things went wrong.

-b (short for beep) rings the terminal bell on a non-zero exit instead of stopping, which is the right choice when you want to keep watching but also want to be told:

$ watch -b -n 30 'systemctl is-active postgresql'

6.3 Precise Intervals

Section 1.3 showed the drift: interval plus runtime, every cycle. -p (short for precise) makes watch aim for the interval itself, subtracting however long the command took. The difference is easy to measure with a command that takes a predictable 0.3 seconds:

$ watch -n 1 'date +%s.%N >> t.log; sleep 0.3'
gaps:      1.303  1.305  1.304  1.303  1.303
fractional: 0.55   0.85   0.15   0.46   0.76      # drifting away

$ watch -p -n 1 'date +%s.%N >> t.log; sleep 0.3'
gaps:      1.001  1.000  1.000  1.000  1.000
fractional: 0.56   0.56   0.56   0.56   0.56      # pinned

Look at the fractional seconds rather than the gaps. Without -p they wander through the whole second. With it they stay put, because watch is now targeting the clock rather than counting from when the last run happened to finish.

Use -p whenever you are correlating what you see with something else on a timer: a cron job, a metrics scrape, a log that rotates on the minute. Skip it otherwise, because it makes no difference for a command that returns instantly.

6.4 Using watch in a Script (and When Not To)

The exit status is where expectations and reality part company. A normal Ctrl-C is not the interesting case; what matters is what the condition options return, and what happens to your command's own exit code:

$ watch -g -n 1 'cat f.txt'        # output changed
$ echo $?
0

$ watch -q 2 -n 0.3 'sh -c "exit 42"'
$ echo $?
0                                  # NOT 42: the command's code is not passed on

$ watch -e -n 0.5 false            # then press a key
$ echo $?
8

That 8 is documented. The manual's exit status table lists it as "getting child process return value with waitpid(2) failed, or command exited up on error", which is the code you get from --errexit doing its job.

The honest conclusion is that watch is a tool for a human at a terminal, and only -g and -q make it script-shaped. For anything unattended, write the loop and keep control of the exit code:

until curl -sf https://example.test/health >/dev/null; do
    sleep 5
done

That loop also works on a BSD, in a container without procps, and on the machine where somebody removed watch. Section 3 covered why that matters.

6.5 What watch Cannot See

watch runs your command with sh -c, and on Debian and Ubuntu /bin/sh is dash, not your interactive Bash. Everything defined only inside your shell is therefore invisible:

$ alias ll='ls -l'
$ watch ll
sh: 1: ll: not found

$ greet() { echo hi; }
$ export -f greet
$ watch greet
sh: 1: greet: not found            # exported bash functions do not reach dash

Environment variables you exported do survive, because those are inherited normally. Aliases, shell functions and shell builtins specific to Bash do not. Write out the real command, or point at a script on disk:

$ watch 'ls -l'
$ watch ~/bin/check-queue.sh
$ watch 'bash -ic "ll"'            # possible, but rarely worth it
Back to top

7. Something Most Users Do Not Know

7.1 A Slow Command Makes watch Lie to You

Section 1.3 showed the interval is a gap. There is a worse version of the same problem, and the manual admits it in the BUGS section rather than hiding it. If the command sometimes takes longer than the interval, watch can end up firing repeatedly to catch up:

BUGS
       --precise mode doesn't yet have advanced temporal distortion technology
       to compensate for a command that takes more than --interval seconds to
       execute.  watch also can get into a state where it rapid-fires as many
       executions of command as it can to catch up from a previous executions
       running longer than --interval (for example, netstat(8) taking ages on
       a DNS lookup).

That is a real hazard when the watched command is expensive. watch -n 1 on a query that usually takes 200ms and occasionally takes four seconds does not politely skip a beat; it can hammer whatever it is querying. Two habits avoid it: set the interval generously above the worst case rather than the typical case, and prefer a cheap proxy for an expensive check. Watching a counter is safer than watching the thing being counted.

7.2 Every Refresh Is a Brand New Process

The PIDs in section 5.1 were not a curiosity, they were the mechanism. Every cycle forks a fresh sh, which forks your command. Nothing carries over between refreshes: no working directory changes, no variables, no shell state at all.

$ watch -t 'echo $$ >> pids.log'
$ cat pids.log
468474
468476
468478
468480                             # a new shell every single time

Three consequences follow. A command with side effects runs those side effects on every refresh, so watch 'curl -X POST ...' is a way to post something several hundred times before lunch. A counter you increment in a variable resets every cycle. And on a short interval you are forking two processes per cycle forever, which is cheap but not free, and is worth remembering before you set -n 0.1 on a busy production box.

7.3 Non-Printing Characters Are Stripped

watch quietly removes control characters from the output rather than passing them through, which is sensible because a stray escape sequence would corrupt the screen it is trying to manage. But it means what you see is not always what the command produced:

$ printf 'a\tb\001c\n' > np.txt      # a, tab, control-A, c
$ cat -A np.txt
a^Ib^Ac$                           # the file really does contain it

# search what reached the terminal for that byte:
$ watch -t cat np.txt
raw 0x01 bytes: 0    visible "^A": 0     # gone without a trace

$ watch -t 'cat -v np.txt'
raw 0x01 bytes: 0    visible "^A": 1     # cat -v turned it into text first

The manual recommends exactly that fix. If you are using watch to hunt down a formatting problem, in a file with odd line endings, in output from a program that is emitting something strange, put cat -v at the end of the pipeline or you will be looking at a cleaned-up version of the evidence.

7.4 watch Is Not a File Watcher

This is the mistake that costs the most time, because watch appears to work. Point it at a build and it does refresh, so nothing looks broken; it is simply doing the wrong job at the wrong moment.

The kernel has an interface for the right job, inotify, and it tells you the instant a file changes instead of up to N seconds later. The tools built on it are the ones you want:

You wantUse
Run a command every N seconds and look at it watch
Run a command the moment a file changes entr, or a framework's own watch mode
Report filesystem events as they happen inotifywait from inotify-tools
Follow a log as lines are appended tail -f, or less +F
Follow the systemd journal journalctl -f
Watch a value change over time, with history viddy or hwatch, which record past output
$ ls *.c | entr -c make            # rebuild the instant a source file is saved
$ inotifywait -m -e close_write /var/spool/incoming

The rule of thumb: if the thing you care about emits an event, listen for the event. Use watch for state that nothing announces, which is most of what a server does. Disk usage, connection counts, queue depth and process lists have no notification mechanism, and polling them on a timer is exactly right.

7.5 It Samples, So It Misses Things

watch shows you the present and forgets it immediately. Both halves of that sentence cost people time.

The forgetting comes first. Suppose load on a server does this:

12:00   0.4
12:01   0.5
12:02   8.7        <- the thing you were called about
12:03   0.6

You start watch uptime at 12:03 and see 0.6. Not "0.6, and it was fine before either": just 0.6, with no way to tell the difference between a server that has been calm all morning and one that fell over sixty seconds ago. -d=permanent helps a little from the moment you start, and no further back.

The missing comes second, and it is the more subtle one. Between two refreshes, watch is not looking. Anything shorter than the interval can happen entirely in the gap:

interval:   |-----2s-----|-----2s-----|-----2s-----|
sampled:    X            X            X
the event:        [==1.5s==]                            never seen

Shortening the interval narrows the gap but never closes it, and it costs more on every cycle. That trade is worth stating plainly because it is not specific to watch at all:

short interval   better resolution, more overhead, more load on what you watch
long interval    less overhead, more that happens invisibly between samples

The same trade governs every metrics system, every SNMP poll and every health check you will ever configure. watch is a good place to meet it, because here the cost of getting it wrong is only a confusing screen.

Two habits follow. When you are investigating something intermittent, log it rather than watch it, so there is a record to read afterwards:

$ while true; do printf '%s %s\n' "$(date +%T)" "$(cut -d' ' -f1 /proc/loadavg)"; sleep 5; done | tee load.log

And when you want to know what happened before you arrived, accept that this is not watch's job. Use whatever already kept the history: sar, the journal, your application's own logs, or a monitoring system. watch answers "what is happening now", and only that.

7.6 Colour Became the Default, and It Changed Old Commands

procps-ng 4.0.4 turned on ANSI interpretation by default. The Debian changelog records it in one line:

$ zcat /usr/share/doc/procps/changelog.Debian.gz | grep -i "watch:"
  * watch: Color support turned on by default use -C to turn off

You can see the switch by counting the colour codes that survive:

# command emits ESC[31m for red
$ watch ...        SGR-31 sequences passed through: 1     # interpreted
$ watch -c ...     SGR-31 sequences passed through: 1     # same, -c is now redundant
$ watch -C ...     SGR-31 sequences passed through: 0     # stripped

Mostly this is an improvement. It bites in one specific place: a command whose output you are reading for the escape codes now has them swallowed. If you were using watch to inspect raw coloured output, add -C. And if you find a tutorial insisting you need -c, it was written before 2023 and the rest of its advice may be equally dated.

7.7 Knowing Where watch Stops

watch is a small tool and it is honest about its size. The point where you should stop stretching it:

When you needReach for
Scrollback, or output taller than the screen viddy, or redirect to a file and read it
To see what the value was five minutes ago viddy, hwatch, or log it and graph it
Alerting when a threshold is crossed A monitoring system, not a terminal
Anything unattended or in a script A shell loop (section 6.4), or a systemd timer
Several values side by side tmux panes, one watch in each
Portability beyond Linux A shell loop; watch is not POSIX

Before any of those, though, there is a cheaper question to ask, and it is the one people skip: does the command already know how to repeat itself? A surprising number do, and their own mode is almost always better than wrapping them:

Instead ofUseWhy it is better
watch free -h free -h -s 2 One process instead of two forks per cycle
watch vmstat vmstat 1 Gives rates, and scrolls so you keep the history
watch iostat iostat -x 2 Same: real rates, with history
watch 'tail /var/log/x' tail -f /var/log/x Instant, and misses nothing between refreshes
watch 'journalctl -n 20' journalctl -f Event-driven, no polling at all
watch docker ps docker stats or docker events Live stream from the daemon
watch kubectl get pods kubectl get pods -w Uses the API's own watch semantics

Look closely at the second and third rows, because they are not just tidier. vmstat 1 and iostat -x 2 print a new line every interval and let it scroll, which means they give you the two things section 7.5 said watch structurally cannot: rates, and a history you can look back through. When a tool offers its own repeat mode, it usually offers those too.

That fifth row is worth trying if you have tmux already. Three panes running watch -d on disk, connections and queue depth make a serviceable dashboard out of tools you have:

$ tmux new-session -d 'watch -d -n 5 df -h'
$ tmux split-window -h 'watch -d -n 2 "ss -tn state established | wc -l"'
$ tmux split-window -v 'watch -d -n 5 "ls /var/spool/queue | wc -l"'
$ tmux attach
Back to top

8. Best Practices

  • Quote the command whenever it contains shell syntax. A pipe, a $, a redirect, a glob, a semicolon. Unquoted, your shell takes it and watch never sees it, and the failure is silent rather than loud.
  • Use -d almost always. Without it you are staring at a wall of text hoping to notice a digit change. With it, your eye is pulled straight to what moved.
  • Reach for -d=permanent when you walk away. It keeps every change since you started, so the screen still has something to tell you ten minutes later.
  • Make the output fit the screen. watch shows the first screenful and silently discards the rest. Pipe through head, tail, wc -l or a grep, and sort so the interesting rows are at the top.
  • Add -w for anything with columns. A truncated table still lines up; a wrapped one is unreadable and eats the vertical space you do not have.
  • Set the interval to the worst case, not the typical one. A command that occasionally takes four seconds should not be on -n 1, or watch will rapid-fire to catch up.
  • Do not point a short interval at anything expensive. Watch a cheap counter rather than the thing being counted, and remember every cycle forks two processes.
  • Never watch a command with side effects. Anything that writes, posts, sends or deletes runs again on every single refresh.
  • Learn -g and -q. "Exit when this changes" and "exit when this settles" replace most of the polling loops people write by hand, and they compose with &&.
  • Use -e when you are hunting an intermittent failure. It freezes the screen at the moment the command failed instead of wiping the evidence two seconds later.
  • Add -p when you are correlating with another clock. A cron job, a metrics scrape, a log that rotates on the minute. Skip it otherwise.
  • Tell the watched command to use colour and skip its pager. --color=always and --no-pager, because from inside watch the command cannot tell it is talking to a screen.
  • Keep secrets out of the command. watch holds its arguments in the process table for the whole run, so a password on the command line is exposed to every user for minutes rather than milliseconds.
  • Ask whether the command repeats itself already. free -s, vmstat 1, iostat -x 2, tail -f, journalctl -f, kubectl -w. Their own mode usually gives rates and history that watch cannot.
  • Reach for /proc and /sys. Those files are generated fresh on every read, so cat plus watch turns most of what the kernel knows into a live display.
  • Write a loop instead for anything unattended. watch does not pass your command's exit code out, and it is not POSIX, so it may not exist on the next machine.
  • Remember it is not a file watcher. If the thing you care about emits an event, use entr, inotifywait or tail -f and get the answer instantly.
  • Read the manual once, properly. It is short, and its BUGS section is unusually honest about the rapid-fire problem and the stripped control characters.
$ man 1 watch              # short, and worth reading end to end
$ watch --help             # the full option list, one screen
$ watch --version          # check before relying on -q, -r or -w
$ man 1 entr               # the event-driven counterpart
$ man 1 tmux               # for several watches side by side
Back to top

9. Common Mistakes

9.1 watch Versus a Shell Loop

Nearly everyone writes the loop before they learn the command, and the loop is a perfectly reasonable thing to write. It is worth being clear about what you gain and what you give up, because the answer is not "the loop is worse".

$ while true; do clear; df -h; sleep 5; done

That is the honest equivalent, and for a quick look it is fine. Here is what watch -n 5 df -h adds:

Featurewatchwhile loop
Redraw without flicker Yes, it repaints in place clear blanks the screen first, so it flickers
Highlight what changed -d Nothing, unless you write a differ
Header with a timestamp Built in Add it yourself
Exit on a condition -g, -q, -e Write the test and break
Interval that accounts for runtime -p Arithmetic you have to do
Custom logic, logging, retries, backoff No Yes, this is where the loop wins
Your command's exit code Not passed out Yours to use
Works on a BSD or in a minimal container No, it is not POSIX Yes
Scrollback None, first screenful only Yes, if you drop the clear

The split is clean once you see it. watch is for looking; a loop is for doing. If a human is going to sit and read the screen, watch -d wins on every point that matters. The moment the answer needs to be acted on, logged, retried or returned to a caller, write the loop, because watch cannot pass the result out (section 6.4).

That last row is easy to forget. Dropping the clear turns the loop into something watch cannot do at all: a scrolling record you can page back through afterwards, which section 7.5 argued is often what you actually wanted.

9.2 Myth Versus Reality

MythReality
"-n 5 runs it every five seconds." It waits five seconds after each run finishes. A two-second command on -n 0.5 cycles every 2.5s.
"watch watches files, like a build tool's watch mode." It knows nothing about files. It runs a command on a timer. For file events use entr or inotifywait (section 7.4).
"watch ls | grep foo filters the listing." Your shell piped watch's own output. The pipe must be inside the quotes, and nothing warns you.
"watch echo $HOME re-evaluates each cycle." Your shell expanded it once, before watch started. Quote it to get a fresh value each time.
"My alias works everywhere, so it works here." watch uses sh -c, which on Debian and Ubuntu is dash. Aliases and shell functions do not exist there.
"The output scrolled off, I can scroll back." There is no scrollback. Anything past the first screenful was never drawn. Make the command produce less.
"watch returns my command's exit code." It does not. watch -q 2 'sh -c "exit 42"' exits 0. Only -e yields a distinct status, and that one is 8.
"-c is how you get colour." Since procps-ng 4.0.4 colour is on by default and -c is redundant. -C is the one that does something now.
"Press q to quit." Ctrl-C. watch is not a pager and reads no keys, except the single keypress that -e waits for.
"The header froze, so watch crashed." The header timestamp updates on each refresh. A frozen one means your command has not returned.
"-p guarantees the interval." It targets the clock instead of counting from the last finish. If the command outlasts the interval, the manual says plainly that it cannot compensate.
"It is standard, so it is on every Unix." watch is not in POSIX. It comes from Linux procps-ng, and it is missing on BSD, on stock macOS and in minimal containers.
"A password on the command line is only exposed for a moment." Not here. watch holds the whole command in its own argv for as long as it runs, so ps shows it to every user on the machine, continuously.
"-d shows me what changed." It compares screen positions. Reorder the rows and it lights up everything, which is why watch -d on a --sort=-%cpu listing is useless (section 4.2).
"A short enough interval will catch it." It narrows the gap and never closes it, and it costs more each cycle. Anything shorter than the interval can still happen entirely unobserved (section 7.5).
"watch can show me the transfer rate." It has no memory of the previous refresh, so it cannot subtract. rx_bytes is a total that only climbs. Use vmstat, iostat or sar for rates (section 5.6).
"What I see is exactly what the command printed." Non-printing characters are stripped. Add cat -v if you are debugging something that hinges on them (section 7.3).

9.3 Other Traps to Avoid

  • Watching something with side effects. watch 'curl -X POST .../deploy' deploys every two seconds. The same goes for anything with rm, mv, INSERT or an email in it.
  • Putting a credential in a watched command. The secret sits in watch's own argument list for the entire session, visible to everyone:
    $ watch -n 2 'mysql -u root -phunter2 -e "SHOW PROCESSLIST"'
    
    # from any other account on the box, for as long as it runs:
    $ ps -ef | grep watch
    pe7er  471478  watch -n 2 mysql -u root -phunter2 -e SHOW PROCESSLIST
    Use a credentials file, a socket, or an environment variable the command reads itself. A one-off command flashes past in the process table; a watch parks it there.
  • Watching a command that waits for input. git log, systemctl status and friends open a pager, which then sits there. Add --no-pager, or set PAGER=cat.
  • Leaving a short interval running for hours. -n 0.1 is twenty processes a second, forever. Fine for a minute of debugging, rude on a shared server overnight.
  • Watching sudo. sudo cannot prompt from inside watch, so unless the credential is already cached every refresh fails the same way: sudo: a password is required. Authenticate first, or start the whole thing with sudo watch ... so the prompt happens once, up front.
  • Forgetting the command is re-parsed every cycle. A relative path is resolved against the directory you started in, which is fine, but a cd inside the command does not persist to the next refresh.
  • Expecting -d to survive a resize. The manual is explicit: on terminal resize the screen is not repainted until the next update, and all highlighting is lost.
  • Using watch as a poor man's monitor. It has no history, no alerting and no record. If it matters at three in the morning, it belongs in a monitoring system.
  • Assuming -x is just a tidier way to quote. It removes the shell completely, so pipes, redirects, globs and variables stop working (section 5.3).
  • Trusting a screenshot of watch without the header. With -t there is no timestamp, so a stale screenshot looks exactly like a live one.
  • Reaching for it on a slow link. Every refresh repaints the whole screen. Over a laggy SSH session a one-second interval is worse than useless; raise the interval and shrink the output.
Back to top

10. Summary

watch is thirty years old, fits on one manual page, and most people use one of its fourteen options. The two ideas that unlock the rest are that the interval is a gap rather than a schedule, and that your command passes through two shells before it runs.

  • watch command runs it every two seconds on a cleared screen. Ctrl-C quits; there is no q.
  • The header shows the interval, the command as watch received it, the hostname and the time of the refresh. A frozen timestamp means your command is hanging.
  • The interval is the pause between runs, not a schedule. Command time is added on top, and a command slower than its interval can make watch rapid-fire to catch up.
  • -n sets the interval, accepts fractions and a comma, has a floor of 0.1s that it applies silently, and can be set once with WATCH_INTERVAL.
  • -d is the option that makes watch worth using, and -d=permanent keeps every change since you started.
  • Quote anything with shell syntax in it. watch echo $$ prints your shell's PID forever; watch echo '$$' prints a new one each cycle; watch -x echo '$$' prints a literal $$. Those three results are the whole model.
  • A pipe outside the quotes is applied to watch itself, and nothing warns you. Put it inside.
  • -x skips the sh -c, which also removes pipes, redirects, globs and variables.
  • You get the first screenful only, with no scrollback. Design the command to fit: head, tail, wc -l, and sort so what matters is on top. -w truncates long lines instead of wrapping them.
  • -g exits when the output changes and -q N exits when it has stopped changing for N cycles. Together they replace most hand-written polling loops.
  • -e freezes the screen on a failure so the evidence survives; -b just beeps and carries on.
  • -p targets the clock rather than counting from the last finish: measured, the fractional seconds go from wandering to pinned at 0.56.
  • watch does not pass your command's exit code out, and it is not POSIX. For anything unattended, write an until loop.
  • It runs sh -c, so your aliases and shell functions do not exist, and every refresh is a brand new pair of processes with no state carried over.
  • Since procps-ng 4.0.4, colour is interpreted by default and -C turns it off. Tutorials telling you to add -c predate 2023.
  • /proc and /sys files are generated on read and report zero bytes, which makes cat under watch a live window on kernel state. But those counters are totals, not rates: watch keeps no memory of the last refresh, so it cannot subtract. Use vmstat, iostat or sar for anything per-second.
  • It samples, so it misses things. Anything shorter than the interval can happen entirely between refreshes, and shortening the interval narrows that gap without ever closing it. It also keeps no history: start watching at 12:03 and the spike at 12:02 never existed.
  • Before wrapping a command, check whether it repeats itself already. free -s, vmstat 1, tail -f, journalctl -f and kubectl -w usually give rates and scrollback too.
  • A loop is for doing, watch is for looking. The loop wins the moment you need logic, logging, retries or the exit code (section 9.1).
  • Never put a credential in a watched command. It sits in the process table, visible to every user, for as long as the watch runs.
  • It is not a file watcher. For events use entr, inotifywait, tail -f or journalctl -f. Use watch for state that nothing announces.

This is the quick reference worth keeping:

THE ESSENTIALS
watch CMD                    every 2s, cleared screen, Ctrl-C to quit
watch -n 5 CMD               interval in seconds (fractions ok, floor 0.1)
watch -d CMD                 highlight what changed  <- use this
watch -d=permanent CMD       keep every change since you started
watch -t CMD                 no header (two more rows)
watch -w CMD                 truncate long lines instead of wrapping
export WATCH_INTERVAL=5      a personal default

QUOTING (the thing that catches everyone)
watch 'ls | head -3'         pipe INSIDE the quotes, or your shell eats it
watch 'echo $HOME'           quoted: re-expanded every cycle
watch echo $HOME             unquoted: expanded ONCE, then frozen
watch -x CMD                 no sh -c at all: no pipes, globs or variables

EXIT ON A CONDITION
watch -g CMD                 exit when the output CHANGES
watch -q 5 CMD               exit when it has NOT changed for 5 cycles
watch -e CMD                 freeze on error, quit on a key press (exit 8)
watch -b CMD                 beep on error, keep going
watch -p -n 1 CMD            aim at the clock, not at the last finish

MAKE IT FIT THE SCREEN (there is no scrollback)
watch 'tail -20 /var/log/nginx/error.log'
watch 'ps aux --sort=-%mem | head -12'
watch 'df -h | grep -E "^/dev|Filesystem"'
watch 'ls -1 /var/spool/queue | wc -l'
watch 'systemctl --no-pager status nginx'
watch 'ls --color=always -l'          # tell the command colour is wanted

DO NOT
watch anything with side effects      it runs again every refresh
watch a command that opens a pager    add --no-pager or PAGER=cat
watch -n 0.1 on a shared server       two forks per cycle, forever
watch expecting an exit code          it does not pass one out
watch expecting file events           use entr / inotifywait / tail -f

KERNEL STATE (these files are generated fresh on every read)
watch -d 'cat /proc/loadavg'
watch -d 'cat /proc/meminfo'
watch -n 2 'cat /proc/mdstat'         a RAID rebuild, the classic use
watch -n 10 'df -ih'                  INODES: free space and free inodes differ
watch -n 5 'cat /sys/class/thermal/thermal_zone0/temp'    millidegrees

CHECK FOR A NATIVE MODE FIRST (usually better than wrapping)
free -h -s 2        vmstat 1        iostat -x 2      sar -n DEV 1
tail -f FILE        journalctl -f   docker stats     kubectl get pods -w

interval = pause BETWEEN runs, never a schedule
frozen header timestamp = your command is hanging, not watch
no memory of the last refresh: watch shows totals, never rates
-d compares screen POSITIONS, so reordered rows light up everything

The quickest way to get value from this is to pick the thing you currently check by pressing the up arrow, and give it a watch -d instead. Disk filling up, a queue draining, a certificate renewing, a deployment coming healthy: those are all one short command and a glance. And if a server is behaving in a way nobody can explain, a couple of watch -d panes on the right counters will often show you the pattern faster than any log will.

Back to top
Linux command: watch
Peter Martin
Peter Martin
Joomla Specialist

Peter is a Joomla specialist and a Linux admin for fast, secure and scalable websites.