300

I need to generate a random port number between 2000-65000 from a shell script. The problem is $RANDOM is a 15-bit number, so I'm stuck!

PORT=$(($RANDOM%63000+2001)) would work nicely if it wasn't for the size limitation.

Does anyone have an example of how I can do this, maybe by extracting something from /dev/urandom and getting it within a range?

2
  • this works for what I need export CUDA_VISIBLE_DEVICES=$((( RANDOM % 8 ))) Commented Mar 11, 2021 at 20:48
  • 1
    you can read about it in this_stackoverflow_question Commented Dec 3, 2024 at 22:35

19 Answers 19

560
shuf -i 2000-65000 -n 1

Enjoy!

Edit: The range is inclusive.

Sign up to request clarification or add additional context in comments.

15 Comments

I think shuf is relatively recent - I've seen it on Ubuntu systems in the last couple years but not the current RHEL/CentOS.
Also, it's probably fine for this use, but I believe shuf does actually permute the entire input. This makes it a bad choice if you're generating the random numbers very frequently.
@Dennis Williamson: Running your test with -n 1 showed negligible time differences, even with end=4000000000. Good to know shuf works smart, not hard :-)
i dont have shuf on my mac :(
@VirenShakya - If you install Homebrew, then you can brew install coreutils. Commands are installed with the prefix g, so it'll be gshuf.
|
99

On Mac OS X and FreeBSD you may also use jot:

jot -r 1  2000 65000

2 Comments

In this example, jot has unfair distribution for the interval's minimum and maximum (i.e., 2000 and 65000). In other words, the min and max will be generated less frequently. See my jot answer for details and a workaround.
jot is also available in most GNU/Linux distributions
63

According to the bash man page, $RANDOM is distributed between 0 and 32767; that is, it is an unsigned 15-bit value. Assuming $RANDOM is uniformly distributed, you can create a uniformly-distributed unsigned 30-bit integer as follows:

$(((RANDOM<<15)|RANDOM))

Since your range is not a power of 2, a simple modulo operation will only almost give you a uniform distribution, but with a 30-bit input range and a less-than-16-bit output range, as you have in your case, this should really be close enough:

PORT=$(( ((RANDOM<<15)|RANDOM) % 63001 + 2000 ))

4 Comments

variable $RANDOM is not always available in all shells. Looking for another solution
If I’m understanding this correctly, you are spreading 32,000 numbers amid a range of 1,000,000,000. But they will only hit on multiples of 2^15—you’re skip-counting by 2^15’s, not filling in all digits between 1 and 2^30 evenly, which is what a uniform distribution is.
@isomorphismes Note that the code references $RANDOM twice. On shells that support $RANDOM, a new value is generated every time it is referenced. So this code fills bits 0 through 14 with one $RANDOM value & fills bits 15 through 29 with another. Assuming $RANDOM is uniform & independent, this covers all values from 0 through 2**30-1 without skipping anything.
@LukasLiesis $RANDOM is always available in Bash. If you're not looking for a Bash solution, I suggest you ask a new question.
52

and here's one with Python

randport=$(python -S -c "import random; print random.randrange(2000,63000)")

and one with awk

awk 'BEGIN{srand();print int(rand()*(63000-2000))+2000 }'

7 Comments

This one gets an upvote from me. I write bash scripts for various systems and I believe awk is probably the most abundant tool for the job. Worked on mac os x and centos without an issue and I know it'll work on my debian machine too, and probably any other normal-ish *nix machine.
However, awk's random seed only seems to refresh once/sec so you might want to a) avoid at all costs or b) re-initialise the seed.
+1 because this seems to be the only POSIX possibility without compilation: RANDOM is not guaranteed by POSIX,
Using the -S option results in ImportError: No module named random. Works if I remove that. Not sure what ghostdog's intention was for that.
python -S -c "import random; print random.randrange(2000,63000)" seems to work fine. However, when I try to get a random number between 1 and 2, I seem to always get 1... Thoughts?
|
19

The simplest general way that comes to mind is a perl one-liner:

perl -e 'print int(rand(65000-2000)) + 2000'

You could always just use two numbers:

PORT=$(($RANDOM + ($RANDOM % 2) * 32768))

You still have to clip to your range. It's not a general n-bit random number method, but it'll work for your case, and it's all inside bash.

If you want to be really cute and read from /dev/urandom, you could do this:

od -A n -N 2 -t u2 /dev/urandom

That'll read two bytes and print them as an unsigned int; you still have to do your clipping.

4 Comments

I used this technique and notice that now and then there will be no number generated, simply blank space.
It requires perl installed. I write a script which should run on most if not all linux machines, sticking with awk version from another answer
Adding random numbers favours middle outcomes at the expense of low or high. It is not uniformly random.
@isomorphismes Yes, if you're literally just adding two random numbers. But, assuming you're referring to the second expression here, that's not what it's doing. It's a random number in [0,32767] plus an independent random choice for the next bit, i.e. 0 or 32768. It's uniform. (It's not ideal for the original question though since you have to clip the range with rerolling.)
15

If you're not a bash expert and were looking to get this into a variable in a Linux-based bash script, try this:

VAR=$(shuf -i 200-700 -n 1)

That gets you the range of 200 to 700 into $VAR, inclusive.

Comments

7

Here's another one. I thought it would work on just about anything, but sort's random option isn't available on my centos box at work.

 seq 2000 65000 | sort -R | head -n 1

2 Comments

sort -R isn't available on OS X either.
@Lri: For what it's worth, sort -R is available on macOS now, 11 years later. :)
7

Same with ruby:

ruby -e 'puts rand(2000...65000)'

Comments

6

You can do this

cat /dev/urandom|od -N2 -An -i|awk -v f=2000 -v r=65000 '{printf "%i\n", f + r * $1 / 65536}'

If you need more details see Shell Script Random Number Generator.

1 Comment

Almost. This gives you a range 2000 to 67000.
5

Bash documentation says that every time $RANDOM is referenced, a random number between 0 and 32767 is returned. If we sum two consecutive references, we get values from 0 to 65534, which covers the desired range of 63001 possibilities for a random number between 2000 and 65000.

To adjust it to the exact range, we use the sum modulo 63001, which will give us a value from 0 to 63000. This in turn just needs an increment by 2000 to provide the desired random number, between 2000 and 65000. This can be summarized as follows:

port=$((((RANDOM + RANDOM) % 63001) + 2000))

Testing

# Generate random numbers and print the lowest and greatest found
test-random-max-min() {
    max=2000
    min=65000
    for i in {1..10000}; do
        port=$((((RANDOM + RANDOM) % 63001) + 2000))
        echo -en "\r$port"
        [[ "$port" -gt "$max" ]] && max="$port"
        [[ "$port" -lt "$min" ]] && min="$port"
    done
    echo -e "\rMax: $max, min: $min"
}

# Sample output
# Max: 64990, min: 2002
# Max: 65000, min: 2004
# Max: 64970, min: 2000

Correctness of the calculation

Here is a full, brute-force test for the correctness of the calculation. This program just tries to generate all 63001 different possibilities randomly, using the calculation under test. The --jobs parameter should make it run faster, but it's not deterministic (total of possibilities generated may be lower than 63001).

test-all() {
    start=$(date +%s)
    find_start=$(date +%s)
    total=0; ports=(); i=0
    rm -f ports/ports.* ports.*
    mkdir -p ports
    while [[ "$total" -lt "$2" && "$all_found" != "yes" ]]; do
        port=$((((RANDOM + RANDOM) % 63001) + 2000)); i=$((i+1))
        if [[ -z "${ports[port]}" ]]; then
            ports["$port"]="$port"
            total=$((total + 1))
            if [[ $((total % 1000)) == 0 ]]; then
                echo -en "Elapsed time: $(($(date +%s) - find_start))s \t"
                echo -e "Found: $port \t\t Total: $total\tIteration: $i"
                find_start=$(date +%s)
            fi
        fi
    done
    all_found="yes"
    echo "Job $1 finished after $i iterations in $(($(date +%s) - start))s."
    out="ports.$1.txt"
    [[ "$1" != "0" ]] && out="ports/$out"
    echo "${ports[@]}" > "$out"
}

say-total() {
    generated_ports=$(cat "$@" | tr ' ' '\n' | \sed -E s/'^([0-9]{4})$'/'0\1'/)
    echo "Total generated: $(echo "$generated_ports" | sort | uniq | wc -l)."
}
total-single() { say-total "ports.0.txt"; }
total-jobs() { say-total "ports/"*; }
all_found="no"
[[ "$1" != "--jobs" ]] && test-all 0 63001 && total-single && exit
for i in {1..1000}; do test-all "$i" 40000 & sleep 1; done && wait && total-jobs

For determining how many iterations are needed to get a given probability p/q of all 63001 possibilities having been generated, I believe we can use the expression below. For example, here is the calculation for a probability greater than 1/2, and here for greater than 9/10.

Expression

8 Comments

You're wrong. $RANDOM is an integer. With your "trick" there are many values that will never be attained. -1.
I'm not sure what you mean with "is an integer", but correct, the algorithm was wrong. Multiplying a random value from a limited range will not increase the range. We need to sum two access to $RANDOM instead, and don't refactor that into a multiplication by two, since $RANDOM is supposed to change on every access. I have updated the answer with the sum version.
Doing RANDOM+RANDOM will not give you a uniform distribution of random numbers between 0 and 65534.
Correct, in other words not all sums have equal chance to occur. In fact, it's fart from that, if we check the graph it's a pyramid! I think this is why I have been getting considerably larger calculation times than the ones expected by the formula above. There's also a problem with the modulo operation: the sums from 63001 to (32767 + 32767) double the chances of occurrence for the first 2534 ports in comparison to the rest of ports. I have been thinking of alternatives, but I think it's better to just start from scratch with a new answer, so I'm voting this one for deletion.
It's like rolling 2 six-sided dice. Statistically it gives you a bell curve: low probability of rolling a "2" or "12", with highest probability of getting a "7" in the middle.
|
5

$RANDOM is a number between 0 and 32767. You want a port between 2000 and 65000. These are 63001 possible ports. If we stick to values of $RANDOM + 2000 between 2000 and 33500, we cover a range of 31501 ports. If we flip a coin and then conditionally add 31501 to the result, we can get more ports, from 33501 to 65001. Then if we just drop 65001, we get the exact coverage needed, with a uniform probability distribution for all ports, it seems.

random-port() {
    while [[ not != found ]]; do
        # 2000..33500
        port=$((RANDOM + 2000))
        while [[ $port -gt 33500 ]]; do
            port=$((RANDOM + 2000))
        done

        # 2000..65001
        [[ $((RANDOM % 2)) = 0 ]] && port=$((port + 31501)) 

        # 2000..65000
        [[ $port = 65001 ]] && continue
        echo $port
        break
    done
}

Testing

i=0
while true; do
    i=$((i + 1))
    printf "\rIteration $i..."
    printf "%05d\n" $(random-port) >> ports.txt
done

# Then later we check the distribution
sort ports.txt | uniq -c | sort -r

Comments

5

Generate random numbers in the range [$floor,$ceil), no dependence:

$(((RANDOM % $(($ceil- $floor))) + $floor))

Generate 100 numbers between 2000 to 65000:

for i in $(seq 100); do echo $(((RANDOM % $((65000 - 2000))) + 2000));done

Comments

3

PORT=$(($RANDOM%63000+2001)) is close to what you want I think.

PORT=$(($RANDOM$RANDOM$RANDOM%63000+2001)) gets around the size limitation that troubles you. Since bash makes no distinctions between a number variable and a string variable, this works perfectly well. The "number" $RANDOM can be concatenated like a string, and then used as a number in a calculation. Amazing!

3 Comments

I see what you're saying. I agree the distribution will be different, but you can't get real randomness anyway. It might be better to sometimes use $RANDOM, sometimes $RANDOM$RANDOM, and sometimes $RANDOM$RANDOM$RANDOM to get a more even distribution. More $RANDOMs favors higher port numbers, as far as I can tell.
( I deleted my original comment, as I used some wrong numerical values and it was too late to edit the comment). Right. x=$(( $n%63000 ) is roughly similar to x=$(( $n % 65535 )); if [ $x -gt 63000 ]; then x=63000.
I wasn't going to criticise (or even do) the math. I simply accepted it. This is what I meant: num=($RANDOM $RANDOM$RANDOM $RANDOM$RANDOM$RANDOM); pick=$(($RANDOM%3)); PORT=$((${num[$pick]}%63000+2001)) --- that seems like a lot of trouble...
2

Or on OS-X the following works for me:

$ gsort --random-sort

Comments

2

You can get the random number through urandom

head -200 /dev/urandom | cksum

Output:

3310670062 52870

To retrieve the one part of the above number.

head -200 /dev/urandom | cksum | cut -f1 -d " "

Then the output is

3310670062

To meet your requirement,

head -200 /dev/urandom |cksum | cut -f1 -d " " | awk '{print $1%63000+2001}'

Comments

1

This is how I usually generate random numbers. Then I use "NUM_1" as the variable for the port number I use. Here is a short example script.

#!/bin/bash

clear
echo 'Choose how many digits you want for port# (1-5)'
read PORT

NUM_1="$(tr -dc '0-9' </dev/urandom | head -c $PORT)"

echo "$NUM_1"

if [ "$PORT" -gt "5" ]
then
clear
echo -e "\x1b[31m Choose a number between 1 and 5! \x1b[0m"
sleep 3
clear
exit 0
fi

Comments

1

If you need a range bigger than 15 bit, dont use the slow unsecure and outdated 15 bit RANDOM, use the fast and secure 32 bit SRANDOM.

SRANDOM are available since about 2021 bash 5.1 roll out.

"one interesting addition to note with Bash 5.1 is the new SRANDOM variable. The SRANDOM variable provides random data from the system's entropy engine and cannot be reseeded. In particular, the SRANDOM variable provides a 32-bit random number that relies upon getrandom/getentropy -- with fall-backs to /dev/urandom or arc4random or even another fallback after that if necessary."

Source: https://www.phoronix.com/news/GNU-Bash-5.1

See what are the different of RANDOM and SRANDOM in bash:

Difference between RANDOM and SRANDOM in Bash

Feel free to improve this answer.

Comments

0

This works for me:

export CUDA_VISIBLE_DEVICES=$((( RANDOM % 8 )))

you can add 1 if you want it to start from 1 instead of 0.

Comments

0

Generating 50 numbers in Bash from a range 100000000000-999999999999 and saving them into a file filename.csv

shuf -i 100000000000-999999999999 -n 50 -o filename.csv

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.