Home Bash TipsHow To Avoid Infinite Until Loops In Bash Scripts Using Timeout

How To Avoid Infinite Until Loops In Bash Scripts Using Timeout

By sk
Published: Updated: 1.5K views 5 mins read

Have you ever written a Bash script that waits for a service to be ready before moving on? That’s a common pattern. But sometimes, that service never comes online. When that happens, your script can get stuck in an infinite loop and you may not even realize it.

This post will show you how to fix that. You'll learn to use the timeout command in Bash to avoid this problem.

Let’s get started.

What is the timeout Command in Linux?

The timeout command lets you run another command with a time limit. If the command does not finish within the given time, timeout stops it automatically.

This is useful when you want to avoid processes running forever, such as stuck loops or commands waiting for input that never comes.

Basic syntax:

timeout [duration] [command] [arguments]

Example:

timeout 5s ping 8.8.8.8

This runs the ping command for 5 seconds, then stops it automatically.

Which Package Provides the timeout Command?

timeout is part of the GNU Coreutils package, which is included by default in most Linux distributions.

If you want to check which package provides the timeout command, you can use this command on Debian-based systems:

dpkg -S $(which timeout)

Sample Output:

coreutils: /usr/bin/timeout

You can check if it’s available on your system with:

command -v timeout && timeout --version

Sample output:

/usr/bin/timeout
timeout (GNU coreutils) 9.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Padraig Brady.

If it’s missing, install it with:

sudo apt install coreutils     # Debian/Ubuntu
sudo dnf install coreutils # Fedora/RHEL 8+
sudo yum install coreutils # Older RHEL/CentOS

A Simple until Loop that Can Go Wrong

For demonstration purpose, let us take PostgreSQL as an example.

Say you're writing a deployment script. Before running a database migration, you want to make sure PostgreSQL is up.

You might write:

until pg_isready -h 127.0.0.1 -p 5432; do
    echo "Waiting for database..."
    sleep 1
done

echo "Database is up! Running migrations..."
./run-migrations.sh

At first glance, this works. But if the database never starts (because of a typo, a crash, or a bad config) your script waits forever. That’s risky, especially in automation.

Say Hello to timeout: A Simple Safety Net

Bash has a built-in way to check things forever, but it doesn't know when to give up. The timeout command fixes that. It runs a command and kills it if it takes too long.

Here’s how it works:

timeout 5s sleep 10

This command starts sleep 10, but after 5 seconds, timeout sends a signal and stops it.

You can also check the exit code afterward:

echo $?
  • 124 means it timed out.
  • 0 means it finished normally.

Why You Can’t Use timeout with until Directly

At this point, you might try something like this:

timeout 30s until pg_isready -h 127.0.0.1 -p 5432; do
    sleep 1
done

But this won’t work. Why? Because until is not a command. It's a Bash keyword. timeout only works with real commands—things that run in a process, like sleep, curl, or ls.

The Right Way to Use timeout in Bash: Wrap It in a Bash Process

To fix this, you need to wrap the loop in a subprocess. Here's the correct approach:

timeout 30s bash -c '
until pg_isready -h 127.0.0.1 -p 5432; do
    echo "Waiting for database..."
    sleep 1
done
'

Now the loop runs in a new Bash process. timeout can monitor that process and kill it if needed.

Verifying until and timeout in Bash

Here’s how you can verify the above PostgreSQL pg_isready example on your local system.

Make sure you have:

  • PostgreSQL installed (pg_isready comes with it).
  • A local or remote PostgreSQL server running (or intentionally not running, to simulate failure).
  • Bash shell (default on most Linux/macOS systems).

1. Confirm pg_isready Works

Run:

pg_isready -h 127.0.0.1 -p 5432

If PostgreSQL is running, output will be:

127.0.0.1:5432 - accepting connections
Confirm pg_isready Works
Confirm pg_isready Works

2. Stop PostgreSQL to Simulate Failure

If you're using systemd:

sudo systemctl stop postgresql

Then run:

pg_isready -h 127.0.0.1 -p 5432

You will get the following output:

127.0.0.1:5432 - no response
Stop PostgreSQL and Check If pg_isready Works
Stop PostgreSQL and Check If pg_isready Works

3. Test until Loop Without Timeout

Run the following code on your Terminal:

until pg_isready -h 127.0.0.1 -p 5432; do
    echo "Waiting for database..."
    sleep 1
done

While PostgreSQL is down, this loops forever.

Test until Loop Without Timeout
Test until Loop Without Timeout

4. Test With timeout + bash -c

Now run the following code in your Terminal:

timeout 10s bash -c '
until pg_isready -h 127.0.0.1 -p 5432; do
    echo "Waiting for database..."
    sleep 1
done
'

Expected behavior:

  • Runs for 10 seconds.
  • Then timeout kills the loop.
  • Exit code will be 124.
$ echo $?
124
Test until Loop With timeout
Test until Loop With timeout

5. Start PostgreSQL and Run Again

sudo systemctl start postgresql

Then run the timeout-wrapped loop again:

timeout 10s bash -c '
until pg_isready -h 127.0.0.1 -p 5432; do
    echo "Waiting for database..."
    sleep 1
done
'

This time, the loop should exit quickly.

Start PostgreSQL and Check If pg_isready Works
Start PostgreSQL and Check If pg_isready Works

Handling Errors Gracefully

You should always check if the loop succeeded or timed out. Here's how:

if timeout 30s bash -c '
until pg_isready -h 127.0.0.1 -p 5432; do
    sleep 1
done
'; then
    echo "Database is up. Starting migration..."
    ./run-migrations.sh
else
    echo "Error: Database did not start in time."
    exit 1
fi

This way, your script either moves forward safely or exits with a clear message.

Bonus: Shorter Alternative Using a Loop with Counter

If you don’t want to use timeout, here’s another way. Loop a fixed number of times:

for i in {1..30}; do
    if pg_isready -h 127.0.0.1 -p 5432; then
        echo "Database is ready."
        ./run-migrations.sh
        exit 0
    fi
    sleep 1
done

echo "Database did not start after 30 seconds."
exit 1

This is pure Bash and works fine for simple cases.

Key Takeaways

  • until is a Bash keyword, not a process. timeout can’t control it directly.
  • Wrap your loop in a bash -c '...' block to use it with timeout.
  • Always check the exit status to know whether the loop succeeded or failed.
  • Use pg_isready, curl, or other tools to check service health.

Conclusion

Infinite loops can break your scripts and waste your time. timeout is a simple tool that gives you control. By wrapping your until loop in a subprocess, you make your script more robust.

It’s a small trick, but one that can save hours of debugging in the future.

If you want to keep your Bash scripts clean and reliable, start using timeout today.

Recommended Read:

You May Also Like

2 comments

anon August 11, 2025 - 10:26 pm

So timeout is a command, but I didn’t see any hint of what package it can be found in. A quick equery belongs (on my gentoo system) says coreutils, which means most traditional Linux systems should have it installed as part of the core system without having to install anything else.

Reply
sk August 12, 2025 - 12:55 pm

Yes, you’re right. The timeout command is provided by the coreutils package. I just updated the guide accordingly.

Reply

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

This website uses cookies to improve your experience. By using this site, we will assume that you're OK with it. Accept Read More