Número aleatório de um intervalo em um Script Bash

196

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

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

Alguém tem um exemplo de como eu posso fazer isso, talvez extraindo algo /dev/urandome colocando-o dentro de um intervalo?

Jason Gooner
fonte

Respostas:

398
shuf -i 2000-65000 -n 1

Aproveitar!

Editar : o intervalo é inclusivo.

leedm777
fonte
7
Eu acho que shufé relativamente recente - eu o vi nos sistemas Ubuntu nos últimos dois anos, mas não no atual RHEL / CentOS.
Cascabel 31/03
5
Além disso, provavelmente é bom para esse uso, mas acredito shufque realmente permita toda a entrada. Isso faz com que seja uma má escolha se você estiver gerando números aleatórios com muita frequência.
Cascabel
3
@Jefromi: No meu sistema, usando este teste time for i in {1..1000}; do shuf -i 0-$end -n 1000 > /dev/null; donee comparando end=1a end=65535mostrou uma melhoria de 25% para a faixa mais curta que ascenderam a cerca de 4 segundos diferença mais de um milhão iterações. E é muito mais rápido do que realizar o cálculo do Bash do OP um milhão de vezes.
Pausado até novo aviso.
8
@ Dennis Williamson: Executando o seu teste com -n 1diferenças de tempo insignificantes mostrou, mesmo com end=4000000000. É bom saber shuftrabalha inteligente, não é difícil :-)
leedm777
6
Eu não tenho shuf no meu mac :(
Viren
79

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

jot -r 1  2000 65000
errno
fonte
5
Neste exemplo, jotpossui distribuição injusta para o mínimo e o máximo do intervalo (ou seja, 2000 e 65000). Em outras palavras, o mínimo e o máximo serão gerados com menos frequência. Veja minha resposta rápida para obter detalhes e uma solução alternativa.
Clint Pachl
jottambém está disponível na maioria das distribuições GNU / Linux
Thor
43

De acordo com a página do manual do bash, $RANDOMé distribuído entre 0 e 32767; isto é, é um valor de 15 bits não assinado. Supondo que $RANDOMseja distribuído uniformemente, você pode criar um número inteiro de 30 bits não assinado e distribuído uniformemente da seguinte maneira:

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

Como seu alcance não é 2, uma operação simples do módulo fornecerá quase uma distribuição uniforme, mas com um intervalo de entrada de 30 bits e um intervalo de saída inferior a 16 bits, como no seu caso, isso realmente deve estar perto o suficiente:

PORT=$(( ((RANDOM<<15)|RANDOM) % 63001 + 2000 ))
Jesin
fonte
1
A variável $RANDOMnem sempre está disponível em todas as conchas. Procurando outra solução
Lukas Liesis
Se estou entendendo isso corretamente, você está distribuindo 32.000 números em um intervalo de 1.000.000.000. Mas eles atingem apenas múltiplos de 2 ^ 15 - você pula contando 2 ^ 15, sem preencher todos os dígitos entre 1 e 2 ^ 30 uniformemente, o que é uma distribuição uniforme.
isomorfismos
@ isomorphismes Observe que o código faz referência $RANDOMduas vezes. Nos shells que suportam $RANDOM, um novo valor é gerado toda vez que é referenciado. Portanto, esse código preenche os bits de 0 a 14 com um $RANDOMvalor e preenche os bits de 15 a 29 com outro. Assumindo que $RANDOMseja uniforme e independente, isso abrange todos os valores de 0 a 2 ** 30-1 sem pular nada.
Jesin
41

e aqui está um com Python

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

e um com awk

awk 'BEGIN{srand();print int(rand()*(63000-2000))+2000 }'
ghostdog74
fonte
6
Este recebe um voto positivo de mim. Escrevo scripts bash para vários sistemas e acredito que o awk é provavelmente a ferramenta mais abundante para o trabalho. Trabalhei no mac os x e centos sem problemas e sei que também funcionará na minha máquina debian, e provavelmente em qualquer outra máquina normal-ish * nix.
John Hunt
6
No entanto, a semente aleatória do awk parece atualizar apenas uma vez / s, portanto, você pode: a) evitar a todo custo ou b) reinicializar a semente.
John Hunt
+1 porque esta parece ser a única possibilidade POSIX sem compilação: RANDOMnão é garantida por POSIX,
Ciro Santilli郝海东冠状病六四事件法轮功
O uso da -Sopção resulta em ImportError: No module named random. Funciona se eu remover isso. Não tenho certeza qual era a intenção do ghostdog para isso.
Chris Johnson
1
python -S -c "import random; print random.randrange(2000,63000)"parece funcionar bem. No entanto, quando tento obter um número aleatório entre 1 e 2, pareço sempre obter 1 ... Pensamentos?
Hubert Léveillé Gauvin
17

A maneira geral mais simples que vem à mente é uma linha de código perl:

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

Você sempre pode usar apenas dois números:

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

Você ainda precisa recortar no seu alcance. Não é um método geral de números aleatórios de n bits, mas funcionará no seu caso e está tudo dentro do bash.

Se você quiser ser realmente engraçado e ler em / dev / urandom, faça o seguinte:

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

Isso lerá dois bytes e os imprimirá como um int não assinado; você ainda precisa fazer o seu recorte.

Cascabel
fonte
Usei essa técnica e observe que, de vez em quando, não haverá número gerado, simplesmente espaço em branco.
PdC
Requer perl instalado. Eu escrevo um script que deve ser executado na maioria, se não todas as máquinas Linux, furando com awka versão de outra resposta
Lukas Liesis
A adição de números aleatórios favorece os resultados médios à custa de baixo ou alto. Não é uniformemente aleatório.
isomorfismos
@ isomorphismes Sim, se você estiver literalmente adicionando apenas dois números aleatórios. Mas, supondo que você esteja se referindo à segunda expressão aqui, não é isso que está fazendo. É um número aleatório em [0,32767] mais uma escolha aleatória independente para o próximo bit, ou seja, 0 ou 32768. É uniforme. (Não é o ideal para a pergunta original, já que você precisa recortar o intervalo com re-rolagem.)
Cascabel
7

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

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

Isso leva a uma faixa de 200 a 700 $VAR, inclusive.

Berto
fonte
5

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

 seq 2000 65000 | sort -R | head -n 1
valadil
fonte
3
sort -Rtambém não está disponível no OS X.
Lri
5

$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 $RANDOM + 2000entre 2000 e 33500 , cobriremos um intervalo de 31501 portas. Se jogarmos uma moeda e adicionarmos condicionalmente 31501 ao resultado, obteremos mais portas, de 33501 a 65001 . Então, se abandonarmos 65001, obteremos a cobertura exata necessária, com uma distribuição de probabilidade uniforme para todas as portas, ao que 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
}

Teste

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
Renato Silva
fonte
5

Você consegue fazer isso

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

Se você precisar de mais detalhes, consulte Gerador de números aleatórios de scripts de shell .

Nova saída
fonte
Quase. Isto dá-lhe uma gama de 2000 a 67000.
Ogre Psalm33
5

Mesmo com ruby:

echo $(ruby -e 'puts rand(20..65)') #=> 65 (inclusive ending)
echo $(ruby -e 'puts rand(20...65)') #=> 37 (exclusive ending)
Lev Lukomsky
fonte
3

A documentação do Bash diz que sempre$RANDOM é referenciada, um número aleatório entre 0 e 32767 é retornado. Se somarmos duas referências consecutivas, obtemos valores de 0 a 65534, que cobrem o intervalo desejado de 63001 possibilidades para um número aleatório entre 2000 e 65000.

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

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

Teste

# 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 possibilidades diferentes aleatoriamente, usando o cálculo em teste. O --jobsparâmetro deve torná-lo 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 determinada probabilidade p/qde todas as 63001 possibilidades terem sido 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


fonte
1
Você está errado. $RANDOMé um número inteiro . Com o seu "truque", existem muitos valores que nunca serão alcançados. -1.
gniourf_gniourf
2
Não sei ao certo o que você quer dizer com "é um número inteiro", mas correto, o algoritmo estava errado. Multiplicar um valor aleatório de um intervalo limitado não aumentará o intervalo. Em $RANDOMvez disso, precisamos somar dois acessos e não refatorá-los para uma multiplicação por dois, pois $RANDOMé suposto mudar a cada acesso. Atualizei a resposta com a versão da soma.
6
Fazendo RANDOM+RANDOMnão vai dar-lhe um uniforme distribuição de números aleatórios entre 0 e 65534.
gniourf_gniourf
3
Correto, em outras palavras, nem todas as somas têm a mesma chance de ocorrer. Na verdade, é um pouco distante disso, se verificarmos o gráfico, é uma pirâmide! Penso que é por isso que tenho tido tempos de cálculo consideravelmente maiores do que os esperados pela fórmula acima. Há também um problema com a operação do módulo: as somas de 63001 a (32767 + 32767) dobram as chances de ocorrência das primeiras 2534 portas em comparação com as demais portas. Estive pensando em alternativas, mas acho melhor começar do zero com uma nova resposta, por isso estou votando nesta questão para exclusão.
4
É como jogar 2 dados de seis lados. Estatisticamente, fornece uma curva de sino: baixa probabilidade de rolar um "2" ou "12", com maior probabilidade de obter um "7" no meio.
Ogre Psalm33
2

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

$ gsort --random-sort
Chadwick Boggs
fonte
2

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 o incomoda. Como o bash não faz distinções entre uma variável numérica e uma variável de sequência, isso funciona perfeitamente bem. O "número" $RANDOMpode ser concatenado como uma string e, em seguida, usado como um número em um cálculo. Surpreendente!

Wastrel
fonte
1
Entendo o que você está dizendo. Concordo que a distribuição será diferente, mas você não pode obter aleatoriedade real de qualquer maneira. Às vezes pode ser melhor usar $ RANDOM, às vezes $ RANDOM $ RANDOM e outras vezes $ RANDOM $ RANDOM $ RANDOM para obter uma distribuição mais uniforme. Mais $ RANDOMs favorecem números de porta mais altos, até onde eu sei.
Wastrel
(Excluí meu comentário original, pois usei alguns valores numéricos incorretos e era tarde demais para editar o comentário). Certo. x=$(( $n%63000 )é aproximadamente semelhante a x=$(( $n % 65535 )); if [ $x -gt 63000 ]; then x=63000.
chepner
Eu não ia criticar (nem fazer) a matemática. Eu simplesmente aceitei. Isto é o que eu quis dizer: num = ($ RANDOM $ RANDOM $ RANDOM $ RANDOM $ RANDOM $ RANDOM); escolher = $ (($ RANDOM% 3)); PORT = $ (($ {num [$ pegar]}% 63000 + 2001)) --- que parece ser um monte de problemas ...
Wastrel
1

Você pode obter o número aleatório através urandom

head -200 /dev/urandom | cksum

Resultado:

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 cumprir sua exigência,

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

zangw
fonte
0

É assim que costumo gerar números aleatórios. Então eu uso "NUM_1" como a variável para o número da porta que eu 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' </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
Yokai
fonte