How to Generate Random Numbers in Bash

How to Generate Random Numbers in Bash

Random numbers show up in more of my scripts than I initially expected — generating test data, picking a random item from a list, adding jitter to a retry delay, sampling files for a spot check, or building games and simulations just for fun. Bash gives you a few different ways to generate randomness, and picking the right one depends entirely on whether you need “good enough for a script” randomness or something cryptographically sound. This article covers both.

The Simple Way: $RANDOM

Bash has a built-in variable called $RANDOM that returns a new pseudo-random integer between 0 and 32767 every time it’s referenced:

echo $RANDOM
echo $RANDOM
echo $RANDOM

Each call produces a different number, e.g.:

14823
2917
30456

Important: $RANDOM is seeded from the shell’s process ID and current time, and its underlying algorithm is a simple, well-documented pseudo-random number generator. It’s fine for scripting convenience but must never be used for anything security-related like passwords, tokens, or cryptographic keys — a topic I cover in more depth in the password generator article in this series.

Generating a Random Number Within a Range

The most common pattern I use is generating a random number between 1 and some maximum, using the modulo operator:

random_number=$(( (RANDOM % 100) + 1 ))
echo "$random_number"

This gives a number between 1 and 100. Breaking it down:

  • RANDOM % 100 gives a remainder between 0 and 99.
  • + 1 shifts the range to 1–100.

For a range with a specific lower and upper bound:

min=10
max=50
random_number=$(( (RANDOM % (max - min + 1)) + min ))
echo "$random_number"

Step-by-Step: A Reusable Random Number Function

#!/usr/bin/env bash

random_between() {
    local min=$1
    local max=$2
    echo $(( (RANDOM % (max - min + 1)) + min ))
}

echo "Random dice roll (1-6): $(random_between 1 6)"
echo "Random port (1024-65535): $(random_between 1024 65535)"

Example output:

Random dice roll (1-6): 4
Random port (1024-65535): 48213

How $RANDOM Works Internally

Bash’s $RANDOM is implemented as a linear congruential-style generator seeded once when the shell starts (based on the process ID and time, unless explicitly reseeded). Every time you reference $RANDOM, Bash advances the internal state and returns the next value in the sequence. You can manually reseed it by assigning a value:

RANDOM=42
echo $RANDOM
echo $RANDOM

Setting a fixed seed like this makes the sequence of subsequent $RANDOM values deterministic and reproducible — genuinely useful for testing, since I can rerun a script and get identical “random” behavior for debugging, but a clear sign this is not meant for security purposes.

A Better Source: /dev/urandom

For numbers that need to be less predictable — or when I need more than 32767 possible values — I read from /dev/urandom instead:

random_number=$(od -An -N4 -tu4 < /dev/urandom | tr -d ' ')
echo "$random_number"

Breaking this down:

  • od -An -N4 -tu4 reads 4 bytes (-N4) from /dev/urandom and formats them (-tu4) as a single unsigned 4-byte integer, without the address column (-An).
  • tr -d ' ' strips any leading whitespace od adds to its output.

This gives numbers across the full 32-bit range (0 to 4,294,967,295), not just 0–32767 like $RANDOM.

To constrain it to a specific range:

min=1
max=1000000
range=$((max - min + 1))
random_number=$(( (RANDOM_SOURCE=$(od -An -N4 -tu4 < /dev/urandom | tr -d ' ')) % range + min ))
echo "$random_number"

Real-World Use Case: Adding Jitter to Retry Logic

When retrying a failed network request, hitting the server at exactly the same interval every time (especially across many clients) can cause a “thundering herd” problem. Adding random jitter spreads out the retries:

#!/usr/bin/env bash
set -euo pipefail

max_attempts=5
base_delay=2

for ((attempt = 1; attempt <= max_attempts; attempt++)); do
    echo "Attempt $attempt..."
    if curl -fs https://api.example.com/health > /dev/null; then
        echo "Success!"
        exit 0
    fi
    jitter=$((RANDOM % 3))
    delay=$((base_delay + jitter))
    echo "Failed, retrying in ${delay}s..."
    sleep "$delay"
done

echo "All attempts failed" >&2
exit 1

Real-World Use Case: Picking a Random Item from a List

#!/usr/bin/env bash

items=("apple" "banana" "cherry" "date" "elderberry")
count=${#items[@]}
index=$((RANDOM % count))

echo "Random pick: ${items[$index]}"

I use this pattern for things like randomly selecting a server from a pool for a quick manual check, or shuffling which test case runs first in a lightweight test harness.

Automation Example: Generating Random Test Data

#!/usr/bin/env bash
set -euo pipefail

OUTPUT_FILE="test_data.csv"
echo "id,value,category" > "$OUTPUT_FILE"

categories=("A" "B" "C" "D")

for ((i = 1; i <= 100; i++)); do
    value=$((RANDOM % 1000))
    category=${categories[$((RANDOM % ${#categories[@]}))]}
    echo "${i},${value},${category}" >> "$OUTPUT_FILE"
done

echo "Generated 100 rows of test data in $OUTPUT_FILE"

This is a pattern I reach for constantly when I need a quick CSV of fake data to test a data pipeline or a reporting script before real data is available.

Shuffling a List

Bash doesn’t have a built-in shuffle, but combining $RANDOM with sort gets the job done:

items=("one" "two" "three" "four" "five")
printf "%s\n" "${items[@]}" | while read -r item; do
    echo "$RANDOM $item"
done | sort -n | cut -d ' ' -f2-

This assigns a random sort key to each item, sorts numerically by that key, then strips the key back off — a classic “randomize by attaching a random sort key” trick. For simple cases, shuf (part of GNU coreutils) does this far more cleanly:

printf "%s\n" "${items[@]}" | shuf

Security Considerations

  • Never use $RANDOM for passwords, tokens, session IDs, or any cryptographic purpose. Its output is predictable if an attacker knows (or can guess) the seed, and the range is far too small (0–32767) for meaningful entropy.
  • Use /dev/urandom (or openssl rand) for anything security-sensitive, as covered in the password generator article — it draws from the kernel’s cryptographically secure random pool.
  • Be aware of modulo bias. Using % range to constrain random numbers introduces a very slight statistical bias unless the range evenly divides the source’s total possible values. For test data and scripting convenience this is irrelevant, but for cryptographic applications it matters, and dedicated tools like openssl rand or language-level crypto libraries handle this correctly.
  • Don’t rely on a fixed seed in production logic. Seeding RANDOM=42 for reproducible testing is fine in a test environment, but if that same fixed seed accidentally ships to production, “random” values become entirely predictable.

Optimization Tips

  • $RANDOM is extremely fast since it’s a shell built-in with no subprocess overhead — prefer it over /dev/urandom reads whenever cryptographic strength isn’t required, especially inside loops.
  • Reading from /dev/urandom via od spawns an external process each time, which adds overhead in tight loops; if you need many random numbers from /dev/urandom, read a larger block once and process it in memory rather than calling od repeatedly.
  • shuf and sort -R are implemented in C and are much faster than manual Bash-loop shuffling for large lists.

Troubleshooting

  • $RANDOM always returns the same number: this usually means RANDOM was explicitly set to a fixed value somewhere earlier in the script (accidentally reusing the variable name), which locks the sequence into a predictable pattern relative to that seed.
  • od command produces unexpected output format: output format flags vary slightly between BSD and GNU od; double check with od --version and adjust flags if running on macOS, or install GNU coreutils via Homebrew.
  • Modulo-based range calculation gives numbers slightly outside the expected bounds: double check your parentheses in the arithmetic expression — operator precedence mistakes here are a common source of off-by-range bugs.

Common Mistakes to Avoid

  • Using $RANDOM for anything security-sensitive — this is the single most common misuse I see in scripts found online.
  • Forgetting that $RANDOM‘s range tops out at 32767, which is too small if you need larger unique identifiers.
  • Reseeding RANDOM accidentally by naming a regular variable RANDOM.
  • Not accounting for modulo bias in contexts where statistical fairness genuinely matters (like a raffle or fair random sampling).

FAQs

Is $RANDOM truly random? No, it’s pseudo-random — deterministic given its internal state, and definitely not suitable for cryptographic use, but perfectly fine for everyday scripting tasks like jitter, sampling, or test data.

How do I generate a random floating-point number in Bash? Bash only handles integers natively; combine $RANDOM with bc or awk for decimals, e.g. awk -v seed="$RANDOM" 'BEGIN{srand(seed); print rand()}'.

What’s the fastest way to generate a large amount of random data? head -c <bytes> /dev/urandom reads raw random bytes directly and is extremely fast for generating bulk random binary data.

Can I generate a random UUID in Bash? Yes, most systems provide uuidgen directly: uuidgen prints a properly formatted random UUID without needing to build one manually from $RANDOM.

Summary

Bash gives you two very different tools depending on your needs: $RANDOM for fast, simple, everyday scripting randomness like jitter, sampling, and test data generation, and /dev/urandom for anything that touches security. Understanding which one to reach for — and never mixing them up — is really the whole story of working with randomness in Bash scripts.

References

  • GNU Bash Reference Manual: https://www.gnu.org/software/bash/manual/bash.html
  • Linux random(4) man page: https://man7.org/linux/man-pages/man4/random.4.html
  • GNU Coreutils manual (shuf, od): https://www.gnu.org/software/coreutils/manual/coreutils.html
  • OpenSSL rand documentation: https://www.openssl.org/docs/man1.1.1/man1/rand.html
Total
2
Shares

Leave a Reply

Previous Post
How to Create a Bash Calculator

How to Create a Bash Calculator

Next Post
How to Work with Dates and Times in Bash

How to Work with Dates and Times in Bash

Related Posts