Waiting for Things: watch, until and timeout

watch reruns a command every few seconds and shows its latest result, an until loop waits until a server answers, and timeout stops a command that takes too long, with exit code 124, so a script never waits forever.

Journeyman · Unix · Linux · Updated

After this lesson you can

  • Rerun a command every few seconds with watch -n, and quote a command that has a pipe
  • Wait for something to be ready with an until loop that checks once a second
  • Stop a command that takes too long with timeout, and tell from exit code 124 that it gave up

Key takeaways

  • watch -n 5 COMMAND reruns the command every 5 seconds and shows its latest output, until you press Ctrl+C. Put a command with a pipe in quotes.
  • An until loop repeats until its command succeeds, so until curl -s URL; do sleep 1; done waits for a server, however long it takes to start.
  • timeout 20 COMMAND stops the command after 20 seconds. Its exit code is then 124, so && and || can tell a command that gave up from one that worked.
  • timeout runs one command, so to put a time limit on a loop, run the loop with bash -c '...'.

You start the website's server and run its tests straight away. Every test fails, because the server is still starting. So you press the up arrow and Enter every few seconds, or you put sleep 10 in your script "to be safe", which is too long on a fast day and too short on a slow one. Checking again and again at a steady pace is called polling, and bash has three tools for it: watch reruns a command for you to look at, an until loop waits in a script, and timeout decides when to give up. Developers use them to wait for servers and rerun tests; administrators use them to watch a disk fill up or a backup finish.

watch: run it again every few seconds

The examples use the bakery site from tmux: Panes, Windows and Sessions. Add test.sh, which asks the server for each page and prints its status code (Talking to the Web: curl):

cd ~/terminal-practice/bakery-site
printf '%s\n' '#!/usr/bin/env bash' '# test.sh: asks the site for each page and prints its status code.' 'for page in index.html menu.html contact.html; do' '  curl -s -o /dev/null -w "%{http_code} $page\n" "http://localhost:8000/$page"' 'done' > test.sh
chmod +x test.sh
./test.sh
000 index.html
000 menu.html
000 contact.html

000 means curl couldn't connect at all, so there's no status code: the server isn't running yet. Start it with ./start-server.sh in a second pane (Ctrl+B % in tmux) or a second terminal, and in the first, let watch run the tests every five seconds:

watch -n 5 ./test.sh
Every 5.0s: ./test.sh                           laptop: Mon Nov  9 15:18:03 2026

200 index.html
200 menu.html
404 contact.html

watch clears the screen and shows the command's latest output, with a header that says how often it runs, the computer's name and the time. -n is the number of seconds between runs; without it, watch waits two. The site has no contact page yet. Make one from a third pane:

printf '%s\n' '<h1>Contact us</h1>' '<p>Call the bakery: 555-0142</p>' > site/contact.html

Within five seconds, the screen changes by itself:

Every 5.0s: ./test.sh                           laptop: Mon Nov  9 15:18:13 2026

200 index.html
200 menu.html
200 contact.html

Press Ctrl+C to stop watching. One trap: put a command with a pipe in quotes, as in watch -n 2 'ls site | wc -l'. Without quotes, the pipe belongs to watch itself: its whole screen goes into wc, and you see nothing at all until you press Ctrl+C.

until: wait until it answers

Line by Line: while read used an until loop to wait for a file. Waiting for a server works the same way, because curl's exit code says whether anything answered (Did It Work? Exit Codes, && and ||). Stop the server with Ctrl+C in its pane, then ask:

curl -s http://localhost:8000/
echo $?
7

Exit code 7 is curl's "couldn't connect"; once the server answers, it's 0. So an until loop can ask once a second and stop as soon as curl succeeds. This starts the server in the background (Background Jobs: &, jobs, fg and bg), waits for it, then runs the tests:

./start-server.sh > server.log 2>&1 &
until curl -s -o /dev/null http://localhost:8000/; do
  echo "Waiting for the server..."
  sleep 1
done
echo "The server is up."
./test.sh
[1] 1943
Waiting for the server...
Waiting for the server...
Waiting for the server...
Waiting for the server...
The server is up.
200 index.html
200 menu.html
200 contact.html

It waited about four seconds, because that's how long the server took, not a guess. kill %1 stops the server when you're done. curl succeeds for any answer, even a 404 page; add -f to wait for a page that works.

timeout: know when to give up

If the server never starts, because of a typo or a port that's already taken, that loop waits forever. timeout sets a limit: timeout 3 COMMAND stops the command after three seconds, and its exit code says so:

timeout 3 sleep 10
echo $?
124

sleep 10 would take ten seconds; timeout stopped it at three, and 124 means "I stopped it". A command that finishes in time keeps its own exit code. timeout runs one command, and a loop isn't one, so hand the loop to bash -c, which runs the text in quotes as a small script of its own. With the server stopped:

timeout 5 bash -c 'until curl -s -o /dev/null http://localhost:8000/; do sleep 1; done' || echo "No answer after 5 seconds"
No answer after 5 seconds

Five seconds later, timeout gave up, its exit code 124 counted as failure, and || ran the message. Start the server first, and the same line prints nothing: the loop ended with 0 as soon as curl got an answer. In a script, the message is followed by exit 1, so the script stops instead of testing a site that isn't there.

A Mac has neither watch nor timeout built in; brew install watch coreutils adds them. Homebrew's coreutils gives timeout under its own name, and as gtimeout too; the commands a Mac already has, such as ls, come only with the g in front (gls).

Check yourself

2 questions to try on the lesson page.

So what

With watch, until and timeout, you stop guessing how long things take: look as often as you like, wait exactly as long as it takes, and give up at a limit you choose.

Key terms

Cheat sheets

Related