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 % 100gives a remainder between 0 and 99.+ 1shifts 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 -tu4reads 4 bytes (-N4) from/dev/urandomand formats them (-tu4) as a single unsigned 4-byte integer, without the address column (-An).tr -d ' 'strips any leading whitespaceodadds 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
$RANDOMfor 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(oropenssl 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
% rangeto 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 likeopenssl randor language-level crypto libraries handle this correctly. - Don’t rely on a fixed seed in production logic. Seeding
RANDOM=42for 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
$RANDOMis extremely fast since it’s a shell built-in with no subprocess overhead — prefer it over/dev/urandomreads whenever cryptographic strength isn’t required, especially inside loops.- Reading from
/dev/urandomviaodspawns 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 callingodrepeatedly. shufandsort -Rare implemented in C and are much faster than manual Bash-loop shuffling for large lists.
Troubleshooting
$RANDOMalways returns the same number: this usually meansRANDOMwas 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.odcommand produces unexpected output format: output format flags vary slightly between BSD and GNUod; double check withod --versionand 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
$RANDOMfor 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
RANDOMaccidentally by naming a regular variableRANDOM. - 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
randdocumentation: https://www.openssl.org/docs/man1.1.1/man1/rand.html