Número random de um intervalo em um script Bash

Eu preciso gerar um número de porta aleatória entre 2000-65000 partir de um script de shell. O problema é $RANDOM é um número de 15 bits, então estou preso!

PORT=$(($RANDOM%63000+2001)) funcionaria muito bem se não fosse pela limitação de tamanho.

Alguém tem um exemplo de como eu posso fazer isso, talvez extraindo algo do /dev/urandom e obtendo-o dentro de um intervalo?

 shuf -i 2000-65000 -n 1 

Apreciar!

Editar : o intervalo é inclusivo.

No Mac OS X e no FreeBSD, você também pode usar o jot:

 jot -r 1 2000 65000 

De acordo com a página man bash, $RANDOM é distribuído entre 0 e 32767; ou seja, é um valor de 15 bits sem sinal. Supondo que $RANDOM seja uniformemente distribuído, você pode criar um inteiro de 30 bits não assinado de distribuição uniforme como segue:

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

Como seu alcance não é uma potência de 2, uma simples operação de módulo quase lhe dará uma distribuição uniforme, mas com um intervalo de input de 30 bits e um intervalo de saída menor que 16 bits, como você tem no seu caso, isso deve estar perto o suficiente:

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

e aqui está um com Python

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

e um com o awk

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

A maneira geral mais simples que vem à mente é um perl one-liner:

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

Você sempre pode usar apenas dois números:

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

Você ainda tem que recortar ao seu alcance. Não é um método geral de números randoms de n bits, mas funciona para o seu caso, e é tudo dentro do bash.

Se você quer ser muito fofo e ler / dev / urandom, você pode fazer isso:

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

Isso vai ler dois bytes e imprimi-los como um int não assinado; você ainda tem que fazer o seu recorte.

Aqui está mais um. Eu pensei que funcionaria em praticamente qualquer coisa, mas a opção aleatória do tipo não está disponível na minha checkbox de centos no trabalho.

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

Se você não é um especialista do bash e estava procurando colocar isso em uma variável em um script bash baseado em Linux, tente isto:

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

Isso dá a você o alcance de 200 a 700 em $VAR , inclusive.

A documentação do Bash diz que toda vez que $RANDOM é referenciado, um número random entre 0 e 32767 é retornado. Se sumrmos duas referências consecutivas, obtemos valores de 0 a 65534, o que abrange o intervalo desejado de 63001 possibilidades para um número random entre 2000 e 65000.

Para ajustá-lo ao intervalo exato, usamos a sum módulo 63001, que nos dará um valor de 0 a 63000. Por sua vez, só precisa de um incremento de 2000 para fornecer o número random desejado, entre 2000 e 65000. Isso pode ser resumido da seguinte forma:

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

Testando

 # 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 

Correção do cálculo

Aqui está um teste completo de força bruta para a correção do cálculo. Este programa apenas tenta gerar todas as 63001 diferentes possibilidades aleatoriamente, usando o cálculo em teste. O parâmetro --jobs deve fazê-lo rodar mais rápido, mas não é determinístico (o total de possibilidades geradas pode ser menor que 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 

Para determinar quantas iterações são necessárias para obter uma dada probabilidade p/q de todas as 63001 possibilidades que foram geradas, acredito que podemos usar a expressão abaixo. Por exemplo, aqui está o cálculo para uma probabilidade maior que 1/2 e aqui para maior que 9/10 .

Expressão

$RANDOM é um número entre 0 e 32767. Você deseja uma porta entre 2000 e 65000. Essas são 63001 portas possíveis. Se mantivermos valores de $RANDOM + 2000 entre 2000 e 33500 , cobriremos um intervalo de 31501 portas. Se apostarmos uma moeda e depois adicionar 31501 ao resultado, poderemos obter mais portas, de 33501 a 65001 . Então, se deixarmos 65001, obteremos a cobertura exata necessária, com uma distribuição de probabilidade uniforme para todas as portas, parece.

 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 } 

Testando

 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 

Você consegue fazer isso

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

Se você precisar de mais detalhes, consulte Gerador de números randoms do Script da Shell .

Ou no OS-X, o seguinte funciona para mim:

 $ gsort --random-sort 

O mesmo com o ruby:

 ZZZ=$(ruby -e 'puts rand(20..65)') echo $ZZZ #=> 56 

PORT=$(($RANDOM%63000+2001)) está perto do que você quer, eu acho.

PORT=$(($RANDOM$RANDOM$RANDOM%63000+2001)) contorna a limitação de tamanho que incomoda. Como o bash não faz distinção entre uma variável numérica e uma variável em string, isso funciona perfeitamente bem. O “número” $RANDOM pode ser concatenado como uma string e usado como um número em um cálculo. Surpreendente!

Você pode obter o número random através do urandom

head -200 /dev/urandom | cksum

Saída:

3310670062 52870

Para recuperar a parte do número acima.

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

Então a saída é

3310670062

Para atender sua exigência,

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

É assim que costumo gerar números randoms. Em seguida, uso “NUM_1” como a variável do número de porta que uso. Aqui está um pequeno exemplo de script.

 #!/bin/bash clear echo 'Choose how many digits you want for port# (1-5)' read PORT NUM_1="$(tr -dc '0-9'