# 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 November 10, 2026

## 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*):

```bash
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
```

```text
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:

```bash
watch -n 5 ./test.sh
```

```text
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:

```bash
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:

```text
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:

```bash
curl -s http://localhost:8000/
echo $?
```

```text
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:

```bash
./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
```

```text
[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:

```bash
timeout 3 sleep 10
echo $?
```

```text
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:

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

```text
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

- [watch](https://www.ask-codey.com/glossary/watch-unix)
- [until Loop](https://www.ask-codey.com/glossary/until-loop)
- [timeout](https://www.ask-codey.com/glossary/timeout)
- [Polling](https://www.ask-codey.com/glossary/polling)
- [Exit Code](https://www.ask-codey.com/glossary/exit-code)

## Cheat sheets

- [Waiting for Things: watch, until and timeout](https://www.ask-codey.com/cheat-sheets/bash-watch-and-wait-cheat-sheet)

## Related

- [tmux: Panes, Windows and Sessions](https://www.ask-codey.com/lessons/bash-tmux)
- [Line by Line: while read](https://www.ask-codey.com/lessons/bash-while-read)
- [Talking to the Web: curl](https://www.ask-codey.com/lessons/bash-curl)
- [Did It Work? Exit Codes, && and ||](https://www.ask-codey.com/lessons/bash-exit-codes)

Source: https://www.ask-codey.com/lessons/bash-watch-and-wait
