Numero casuale da un intervallo in uno script Bash


196

Devo generare un numero di porta casuale tra 2000-65000uno script di shell. Il problema è che $RANDOMun numero di 15 bit, quindi sono bloccato!

PORT=$(($RANDOM%63000+2001)) funzionerebbe bene se non fosse per la limitazione delle dimensioni.

Qualcuno ha un esempio di come posso farlo, magari estraendo qualcosa /dev/urandome facendolo rientrare in un intervallo?

Risposte:


398
shuf -i 2000-65000 -n 1

Godere!

Modifica : l'intervallo è inclusivo.


7
Penso che shufsia relativamente recente - l'ho visto su sistemi Ubuntu negli ultimi due anni, ma non l'attuale RHEL / CentOS.
Cascabel,

5
Inoltre, probabilmente va bene per questo uso, ma credo shufche permetta effettivamente l'intero input. Questo rende una scelta sbagliata se stai generando numeri casuali molto frequentemente.
Cascabel,

3
@Jefromi: Sul mio sistema, con questo test time for i in {1..1000}; do shuf -i 0-$end -n 1000 > /dev/null; donee il confronto end=1ad end=65535mostrato un miglioramento del 25% per la fascia più breve, che ammontano a circa 4 secondi differenza oltre un milione di iterazioni. Ed è molto più veloce rispetto all'esecuzione del calcolo Bash del PO un milione di volte.
In pausa fino a nuovo avviso.

8
@Dennis Williamson: l'esecuzione del test ha -n 1mostrato differenze di tempo trascurabili, anche con end=4000000000. Buono a sapersi shuffunziona in modo intelligente, non difficile :-)
leedm777

6
non ho shuf sul mio mac :(
Viren,

79

Su Mac OS X e FreeBSD puoi anche usare jot:

jot -r 1  2000 65000

5
In questo esempio, jotha una distribuzione sleale per il minimo e il massimo dell'intervallo (ovvero 2000 e 65000). In altre parole, il minimo e il massimo saranno generati meno frequentemente. Vedi la mia risposta jot per i dettagli e una soluzione alternativa.
Clint Pachl,

jotè disponibile anche nella maggior parte delle distribuzioni GNU / Linux
Thor,

43

Secondo la pagina man di bash, $RANDOMè distribuito tra 0 e 32767; cioè, è un valore senza segno a 15 bit. Supponendo che $RANDOMsia distribuito uniformemente, è possibile creare un intero senza segno a 30 bit distribuito uniformemente come segue:

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

Poiché il tuo intervallo non è una potenza di 2, una semplice operazione modulo ti darà quasi una distribuzione uniforme, ma con un intervallo di input di 30 bit e un intervallo di output inferiore a 16 bit, come nel tuo caso, questo dovrebbe davvero essere abbastanza vicino:

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

1
La variabile $RANDOMnon è sempre disponibile in tutte le shell. Alla ricerca di un'altra soluzione
Lukas Liesis,

Se lo capisco correttamente, stai diffondendo 32.000 numeri in un intervallo di 1.000.000.000. Ma colpiranno solo su multipli di 2 ^ 15 — stai saltando il conteggio per 2 ^ 15, non riempi tutte le cifre tra 1 e 2 ^ 30 in modo uniforme, che è una distribuzione uniforme.
isomorfismi

@isomorphismes Nota che il codice fa riferimento $RANDOMdue volte. Sulle shell che supportano $RANDOM, un nuovo valore viene generato ogni volta che viene referenziato. Quindi questo codice riempie i bit da 0 a 14 con un $RANDOMvalore e riempie i bit da 15 a 29 con un altro. Supponendo che $RANDOMsia uniforme e indipendente, questo copre tutti i valori da 0 a 2 ** 30-1 senza saltare nulla.
Jesin,

41

ed eccone uno con Python

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

e uno con awk

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

6
Questo ottiene un voto da parte mia. Scrivo script bash per vari sistemi e credo che awk sia probabilmente lo strumento più abbondante per il lavoro. Ha funzionato su mac os x e centos senza problemi e so che funzionerà anche sulla mia macchina debian, e probabilmente su qualsiasi altra macchina normale.
John Hunt,

6
Tuttavia, il seme casuale di awk sembra aggiornarsi solo una volta / sec, quindi potresti voler a) evitare a tutti i costi o b) reinizializzare il seme.
John Hunt,

+1 perché questa sembra essere l'unica possibilità POSIX senza compilazione: RANDOMnon è garantita da POSIX,
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

L'uso -Sdell'opzione risulta in ImportError: No module named random. Funziona se lo rimuovo. Non sono sicuro di quale fosse l'intenzione del fantasma.
Chris Johnson,

1
python -S -c "import random; print random.randrange(2000,63000)"sembra funzionare bene. Tuttavia, quando provo a ottenere un numero casuale compreso tra 1 e 2, mi sembra sempre di ottenere 1 ... Pensieri?
Hubert Léveillé Gauvin,

17

Il modo generale più semplice che viene in mente è un perl one-liner:

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

Puoi sempre usare solo due numeri:

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

Devi ancora agganciare al tuo intervallo. Non è un metodo numerico casuale n-bit, ma funzionerà per il tuo caso, ed è tutto all'interno di bash.

Se vuoi essere davvero carino e leggere da / dev / urandom, puoi farlo:

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

Questo leggerà due byte e li stamperà come un int senza segno; devi ancora fare il ritaglio.


Ho usato questa tecnica e noto che di tanto in tanto non verrà generato alcun numero, semplicemente uno spazio vuoto.
PdC,

Richiede perl installato. Scrivo uno script che dovrebbe essere eseguito sulla maggior parte se non su tutte le macchine linux, awk
rispettando la

L'aggiunta di numeri casuali favorisce risultati intermedi a scapito di basso o alto. Non è uniformemente casuale.
isomorfismi

@isomorfismi Sì, se stai letteralmente aggiungendo solo due numeri casuali. Ma, supponendo che ti riferisca alla seconda espressione qui, non è quello che sta facendo. È un numero casuale in [0,32767] più una scelta casuale indipendente per il bit successivo, ovvero 0 o 32768. È uniforme. (Non è l'ideale per la domanda originale, dal momento che devi tagliare l'intervallo con il rilancio.)
Cascabel,

7

Se non sei un esperto di bash e stavi cercando di inserirlo in una variabile in uno script bash basato su Linux, prova questo:

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

Questo ti porta nell'intervallo compreso tra 200 e 700 $VARinclusi.


5

Eccone un altro. Ho pensato che avrebbe funzionato praticamente su qualsiasi cosa, ma l'opzione casuale di sort non è disponibile sul mio centos box al lavoro.

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

3
sort -Rnon è disponibile nemmeno su OS X.
Lri,

5

$RANDOMè un numero compreso tra 0 e 32767. Si desidera una porta tra 2000 e 65000. Sono 63001 possibili porte. Se manteniamo valori compresi $RANDOM + 2000tra 2000 e 33500 , copriamo un intervallo di 31501 porte. Se lanciamo una moneta e quindi aggiungiamo condizionalmente 31501 al risultato, possiamo ottenere più porte, da 33501 a 65001 . Quindi se lasciamo cadere 65001, otteniamo l'esatta copertura necessaria, con una distribuzione di probabilità uniforme per tutte le porte, a quanto pare.

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
}

analisi

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

Lo stesso con il rubino:

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

3

La documentazione di Bash afferma che ogni volta che $RANDOMviene fatto riferimento, viene restituito un numero casuale compreso tra 0 e 32767. Se sommiamo due riferimenti consecutivi, otteniamo valori da 0 a 65534, che copre l'intervallo desiderato di 63001 possibilità per un numero casuale compreso tra 2000 e 65000.

Per adattarlo all'intervallo esatto, utilizziamo la somma modulo 63001, che ci darà un valore compreso tra 0 e 63000. Ciò a sua volta richiede solo un incremento di 2000 per fornire il numero casuale desiderato, tra 2000 e 65000. Questo può essere riassunto come segue:

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

analisi

# 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

Correttezza del calcolo

Ecco un test completo a forza bruta per la correttezza del calcolo. Questo programma cerca solo di generare casualmente tutte le 63001 diverse possibilità, usando il calcolo sotto test. Il --jobsparametro dovrebbe renderlo più veloce, ma non è deterministico (il totale delle possibilità generate potrebbe essere inferiore a 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

Per determinare quante iterazioni sono necessarie per ottenere una determinata probabilità che p/qsiano state generate tutte le 63001 possibilità, credo che possiamo usare l'espressione sotto. Ad esempio, ecco il calcolo per una probabilità maggiore di 1/2 e qui per maggiore di 9/10 .

Espressione


1
Hai torto. $RANDOMè un numero intero . Con il tuo "trucco" ci sono molti valori che non saranno mai raggiunti. -1.
gniourf_gniourf,

2
Non sono sicuro di cosa intendi con "è un numero intero", ma corretto, l'algoritmo era sbagliato. Moltiplicare un valore casuale da un intervallo limitato non aumenterà l'intervallo. Dobbiamo invece sommare due accessi $RANDOMe non trasformarlo in una moltiplicazione per due, poiché $RANDOMsi suppone che cambi ad ogni accesso. Ho aggiornato la risposta con la versione di somma.

6
Fare RANDOM+RANDOMnon ti darà una distribuzione uniforme di numeri casuali tra 0 e 65534.
gniourf_gniourf

3
Corretto, in altre parole non tutte le somme hanno pari possibilità di verificarsi. In effetti, è scoreggia da quello, se controlliamo il grafico è una piramide! Penso che questo sia il motivo per cui sto ottenendo tempi di calcolo considerevolmente più grandi di quelli previsti dalla formula sopra. C'è anche un problema con l'operazione modulo: le somme da 63001 a (32767 + 32767) raddoppiano le possibilità che si verifichino per le prime 2534 porte rispetto alle altre porte. Ho pensato ad alternative, ma penso che sia meglio iniziare da zero con una nuova risposta, quindi sto votando questa per l'eliminazione.

4
È come tirare 2 dadi a sei facce. Statisticamente ti dà una curva a campana: bassa probabilità di rotolare un "2" o "12", con la più alta probabilità di ottenere un "7" nel mezzo.
Ogre Salmo33


2

PORT=$(($RANDOM%63000+2001)) è vicino a quello che vuoi, penso.

PORT=$(($RANDOM$RANDOM$RANDOM%63000+2001))aggira la limitazione dimensionale che ti disturba. Poiché bash non fa distinzioni tra una variabile numerica e una variabile stringa, questo funziona perfettamente. Il "numero" $RANDOMpuò essere concatenato come una stringa e quindi utilizzato come numero in un calcolo. Sorprendente!


1
Capisco quello che stai dicendo. Sono d'accordo che la distribuzione sarà diversa, ma non puoi comunque ottenere una vera casualità. Potrebbe essere meglio usare a volte $ RANDOM, a volte $ RANDOM $ RANDOM, e talvolta $ RANDOM $ RANDOM $ RANDOM per ottenere una distribuzione più uniforme. Più $ RANDOM favorisce numeri di porta più alti, per quanto ne so.
Wastrel,

(Ho eliminato il mio commento originale, poiché ho usato alcuni valori numerici errati ed era troppo tardi per modificare il commento). Destra. x=$(( $n%63000 )è approssimativamente simile a x=$(( $n % 65535 )); if [ $x -gt 63000 ]; then x=63000.
Chepner,

Non avevo intenzione di criticare (o addirittura fare) la matematica. L'ho semplicemente accettato. Questo è ciò che intendevo dire: num = ($ RANDOM $ RANDOM $ RANDOM $ RANDOM $ RANDOM $ RANDOM); raccogliere = $ (($ RANDOM% 3)); PORT = $ (($ {num [$ pick]}% 63000 + 2001)) --- sembra un sacco di problemi ...
Wastrel,

1

Puoi ottenere il numero casuale attraverso urandom

head -200 /dev/urandom | cksum

Produzione:

3310670062 52870

Per recuperare una parte del numero sopra.

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

Quindi l'output è

3310670062

Per soddisfare le vostre esigenze,

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


0

Ecco come di solito generi numeri casuali. Quindi uso "NUM_1" come variabile per il numero di porta che utilizzo. Ecco un breve script di esempio.

#!/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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.