275

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?

rpr
  • 155
  • 5
Jason Gooner
  • 3,071
  • 3
  • 20
  • 13

19 Answers19

527
shuf -i 2000-65000 -n 1

Enjoy!

Edit: The range is inclusive.

leedm777
  • 23,444
  • 10
  • 58
  • 87
  • 9
    I think `shuf` is relatively recent - I've seen it on Ubuntu systems in the last couple years but not the current RHEL/CentOS. – Cascabel Mar 31 '10 at 20:37
  • 5
    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. – Cascabel Mar 31 '10 at 20:42
  • 4
    @Jefromi: On my system, using this test `time for i in {1..1000}; do shuf -i 0-$end -n 1000 > /dev/null; done` and comparing `end=1` to `end=65535` showed about a 25% improvement for the shorter range which amounted to about 4 seconds difference over a million iterations. And it's **lots** faster than performing the OP's Bash calculation a million times. – Dennis Williamson Mar 31 '10 at 21:57
  • 3
    @Dennis Williamson: Thanks for the benchmark. I was on the CentOS system at the time and couldn't test for sure; I figured it couldn't be that bad since it was within the C code. I really just wanted to point out that it's not actually just generating a single random number. – Cascabel Mar 31 '10 at 23:46
  • 10
    @Dennis Williamson: Running your test with `-n 1` showed negligible time differences, even with `end=4000000000`. Good to know `shuf` works smart, not hard :-) – leedm777 Apr 01 '10 at 20:59
  • 8
    i dont have shuf on my mac :( – Viren Jul 21 '12 at 09:36
  • 7
    @VirenShakya - If you install [Homebrew](http://mxcl.github.com/homebrew/), then you can `brew install coreutils`. Commands are installed with the prefix `g`, so it'll be `gshuf`. – leedm777 Jul 23 '12 at 17:19
  • how can I tell shuff to print random in 4 digits? for example 0004 or 0234 Or 8623? – MLSC Mar 18 '14 at 11:02
  • @MortezaLSC There's probably not a straightforward answer in bash. I'd ask a new question. – leedm777 Mar 18 '14 at 20:37
  • Perfect thanks! My script was hanging on `backupDst="$backup_dir/$backup_name$(date +%d%m%y)_$(cat /dev/urandom | tr -dc 'a-z' | fold -w 4 | head -n 1).xlsm"` with `cat /dev/urandom` being the culprit. Using `backupDst="$backup_dir/$backup_name$(date +%d%m%y)_$(shuf -i '1000-9999' -n '1').xlsm"` fixed it right away. Cheers! – Sven M. Oct 31 '15 at 16:10
  • 1
    @MortezaLSC: utter bash amateur here but this would be one way: `d1=$(shuf -i '0-9' -n '1') d2=$(shuf -i '0-9' -n '1') d3=$(shuf -i '0-9' -n '1') d4=$(shuf -i '0-9' -n '1') echo $d1$d2$d3$d4` Pretty sure there is more elegant ways especially in terms of DRY but this allows for random numbers with 4 digits with leading zeros. – Sven M. Oct 31 '15 at 16:18
  • Sometimes when I run the following I get back numbers less than 100. What gives? shuf -i 100-800 -n 1 – Rome_Leader Mar 05 '18 at 19:20
  • 2
    `shuf` [doesn't use a cryptographically secure random number generator](https://www.gnu.org/software/coreutils/manual/html_node/Random-sources.html) by default. Add `--random-source=/dev/urandom` if that's what you need. – Boris Verkhovskiy May 19 '19 at 23:41
  • this works for what I need `export CUDA_VISIBLE_DEVICES=$((( RANDOM % 8 )))` – Charlie Parker Mar 11 '21 at 20:48
  • Beware. Interesting that `shuf -i 101-100 -n 1` returns nothing. Like, if the *low* range number is *higher* by 1 than the high number, the program returns nothing (with exit code = 0), while `102-100` results in an error and `100-100` returns 100 as expected. Looks like an ancient bug. Opened a bug report, regarding the issue: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=49217 – Artfaith Jun 25 '21 at 10:59
93

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

jot -r 1  2000 65000
errno
  • 931
  • 5
  • 2
  • 5
    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](http://unix.stackexchange.com/a/241199/141917) for details and a workaround. – Clint Pachl Nov 06 '15 at 10:02
  • `jot` is also available in most GNU/Linux distributions – Thor May 25 '18 at 16:55
60

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 ))
Diosney
  • 10,520
  • 15
  • 66
  • 111
Jesin
  • 1,009
  • 9
  • 12
  • 1
    variable `$RANDOM` is not always available in all shells. Looking for another solution – Lukas Liesis Oct 31 '18 at 07:25
  • 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 May 09 '20 at 16:51
  • @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. – Jesin May 18 '20 at 20:55
48

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 }'
ghostdog74
  • 327,991
  • 56
  • 259
  • 343
  • 7
    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. – John Hunt Aug 02 '13 at 08:48
  • 7
    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. – John Hunt Aug 02 '13 at 08:50
  • 1
    +1 because this seems to be the only POSIX possibility without compilation: `RANDOM` is not guaranteed by POSIX, – Ciro Santilli OurBigBook.com Apr 28 '15 at 14:02
  • 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. – Chris Johnson Feb 04 '17 at 16:28
  • 1
    `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? – Hubert Léveillé Gauvin Dec 14 '17 at 16:07
  • Using `random.randint` instead of `random.randrange` works. – Hubert Léveillé Gauvin Dec 14 '17 at 16:26
  • @JohnHunt If you don't provide a seed to `srand`, it will use the current date and time. Found here (has more info also) https://stackoverflow.com/a/4048538/1737158 – Lukas Liesis Oct 31 '18 at 07:28
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.

Cascabel
  • 479,068
  • 72
  • 370
  • 318
  • I used this technique and notice that now and then there will be no number generated, simply blank space. – Paulie-C Apr 29 '18 at 12:31
  • 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 – Lukas Liesis Oct 31 '18 at 07:29
  • Adding random numbers favours middle outcomes at the expense of low or high. It is not uniformly random. – isomorphismes May 11 '20 at 03:26
  • @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.) – Cascabel May 11 '20 at 06:34
14

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.

Berto
  • 702
  • 2
  • 9
  • 19
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
valadil
  • 1,648
  • 1
  • 14
  • 30
6

Same with ruby:

echo $(ruby -e 'puts rand(20..65)') #=> 65 (inclusive ending)
echo $(ruby -e 'puts rand(20...65)') #=> 37 (exclusive ending)
Lev Lukomsky
  • 6,346
  • 4
  • 34
  • 24
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

  • 1
    You're wrong. `$RANDOM` is _an integer_. With your "trick" there are many values that will never be attained. `-1`. – gniourf_gniourf Dec 31 '12 at 20:56
  • 2
    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. –  Jan 02 '13 at 21:54
  • 1
    Are you aware that doing so, you don't have a uniform distribution of the random numbers? – gniourf_gniourf Jan 06 '13 at 17:20
  • Uniform distribution? Doing so what, the correctness test? I don't understand what you mean. –  Jan 07 '13 at 18:33
  • 6
    Doing `RANDOM+RANDOM` will not give you a _uniform_ distribution of random numbers between 0 and 65534. – gniourf_gniourf Jan 07 '13 at 19:47
  • 3
    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. –  Jan 13 '13 at 16:04
  • 4
    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. – Ogre Psalm33 Apr 29 '13 at 13:11
  • `$(( RANDOM * 2 + RANDOM % 2 ))` would give an even distribution from 0 to 65535, wouldn't it? `RANDOM * 2` would evenly distribute the *even* numbers between 0 and 65534, and then `RANDOM % 2` would add a 0 or a 1 to it. Using his technique, it should be possible to get larger uniform ranges of random numbers by increasing the factor and divisor (2). – mogsie Jan 10 '20 at 12:39
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
5

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.

Thor
  • 45,082
  • 11
  • 119
  • 130
New Exit
  • 51
  • 1
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!

Kijewski
  • 25,517
  • 12
  • 101
  • 143
Wastrel
  • 39
  • 1
  • 1
    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. – Wastrel Jul 14 '12 at 16:22
  • ( 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`. – chepner Jul 14 '12 at 16:34
  • 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... – Wastrel Jul 14 '12 at 16:41
2

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
Shiming
  • 121
  • 6
2

Or on OS-X the following works for me:

$ gsort --random-sort
joran
  • 169,992
  • 32
  • 429
  • 468
1

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}'

zangw
  • 43,869
  • 19
  • 177
  • 214
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
Thor
  • 45,082
  • 11
  • 119
  • 130
Yokai
  • 1,170
  • 13
  • 17
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.

Charlie Parker
  • 5,884
  • 57
  • 198
  • 323
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
Sergey Nemchinov
  • 1,348
  • 15
  • 21
0

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.

Alfred.37
  • 181
  • 1
  • 12