Losowa liczba z zakresu w skrypcie Bash

197

Muszę wygenerować losowy numer portu pomiędzy 2000-65000skryptem powłoki. Problemem jest $RANDOM15-bitowa liczba, więc utknąłem!

PORT=$(($RANDOM%63000+2001)) działałby ładnie, gdyby nie ograniczenie wielkości.

Czy ktoś ma przykład, w jaki sposób mogę to zrobić, może poprzez wyciągnięcie czegoś /dev/urandomi umieszczenie go w zasięgu?

Jason Gooner
źródło

Odpowiedzi:

398
shuf -i 2000-65000 -n 1

Cieszyć się!

Edycja : zakres obejmuje.

leedm777
źródło
7
Myślę, że shufjest stosunkowo nowy - widziałem go na systemach Ubuntu w ciągu ostatnich kilku lat, ale nie na obecnym RHEL / CentOS.
Cascabel
5
Prawdopodobnie jest w porządku do tego zastosowania, ale uważam, shufże faktycznie przenika całe dane wejściowe. To sprawia, że ​​jest to zły wybór, jeśli bardzo często generujesz liczby losowe.
Cascabel
3
@Jefromi: W moim systemie, za pomocą tego testu time for i in {1..1000}; do shuf -i 0-$end -n 1000 > /dev/null; donei porównując end=1do end=65535wykazała poprawę o 25% w przypadku krótszego zasięgu, które wyniosły około 4 sekundy różnicy ponad miliona iteracji. Jest to o wiele szybsze niż wykonywanie obliczenia Bash PO milion razy.
Wstrzymano do odwołania.
9
@Dennis Williamson: Przeprowadzenie testu -n 1wykazało znikome różnice czasowe, nawet z end=4000000000. Dobrze wiedzieć, shufdziała dobrze , a nie ciężko :-)
leedm777,
6
Nie mam ShUF na moim komputerze Mac :(
Viren
79

W systemach Mac OS X i FreeBSD możesz także użyć jot:

jot -r 1  2000 65000
errno
źródło
5
W tym przykładzie jotma niesprawiedliwy rozkład dla minimum i maksimum przedziału (tj. 2000 i 65000). Innymi słowy, min i max będą generowane rzadziej. Zobacz moją odpowiedź, aby uzyskać szczegółowe informacje i obejście.
Clint Pachl
jotjest również dostępny w większości dystrybucji GNU / Linux
Thor
43

Według strony podręcznika bash, $RANDOMjest on dystrybuowany między 0 a 32767; to jest 15-bitowa wartość bez znaku. Zakładając, że $RANDOMjest równomiernie rozłożona, możesz utworzyć równomiernie rozłożoną 30-bitową liczbę całkowitą bez znaku w następujący sposób:

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

Ponieważ twój zakres nie jest potęgą 2, prosta operacja modulo da prawie tylko jednolity rozkład, ale z 30-bitowym zakresem wejściowym i mniej niż 16-bitowym zakresem wyjściowym, jak masz w tym przypadku, to naprawdę powinno być wystarczająco blisko:

PORT=$(( ((RANDOM<<15)|RANDOM) % 63001 + 2000 ))
Jesin
źródło
1
zmienna $RANDOMnie zawsze jest dostępna we wszystkich powłokach. Szukasz innego rozwiązania
Lukas Liesis
Jeśli dobrze to rozumiem, rozprowadzasz 32 000 liczb w zakresie 1 000 000 000. Ale uderzą tylko w wielokrotności 2 ^ 15 - odliczasz pomijanie o 2 ^ 15, nie wypełniając równomiernie wszystkich cyfr od 1 do 2 ^ 30, co jest równomiernym rozkładem.
izomorfizmy
@ isomorphismes Uwaga: kod odwołuje się $RANDOMdwukrotnie. W obsługiwanych powłokach $RANDOMnowa wartość jest generowana za każdym razem, gdy się do niej odwołuje. Więc ten kod wypełnia bity od 0 do 14 jedną $RANDOMwartością, a bity od 15 do 29 inną. Zakładając, że $RANDOMjest jednolity i niezależny, obejmuje to wszystkie wartości od 0 do 2 ** 30-1 bez pomijania czegokolwiek.
Jesin
41

a oto jeden z Pythonem

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

i jeden z awk

awk 'BEGIN{srand();print int(rand()*(63000-2000))+2000 }'
ghostdog74
źródło
6
Ten dostaje ode mnie opinię. Piszę skrypty bash dla różnych systemów i uważam, że awk jest prawdopodobnie najobszerniejszym narzędziem do tego zadania. Pracowałem na Mac OS X i Centos bez problemu i wiem, że zadziała również na mojej maszynie Debiana i prawdopodobnie na każdej innej normalnej maszynie * Nix.
John Hunt
6
Jednak losowe ziarno awk wydaje się odświeżać tylko raz na sekundę, więc możesz a) uniknąć za wszelką cenę lub b) ponownie zainicjować ziarno.
John Hunt
+1, ponieważ wydaje się, że jest to jedyna możliwość POSIX bez kompilacji: RANDOMPOSIX nie gwarantuje,
Ciro Santilli 28 冠状 病 六四 事件 法轮功
Korzystanie z -Sopcji powoduje ImportError: No module named random. Działa, jeśli to usunę. Nie jestem pewien, jaki był zamiar ghostdog.
Chris Johnson
1
python -S -c "import random; print random.randrange(2000,63000)"wydaje się działać dobrze. Jednak gdy próbuję uzyskać losową liczbę od 1 do 2, wydaje mi się, że zawsze otrzymuję 1 ... Myśli?
Hubert Léveillé Gauvin
17

Najprostszym ogólnym sposobem, jaki przychodzi mi na myśl, jest perl-one-liner:

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

Zawsze możesz po prostu użyć dwóch liczb:

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

Nadal musisz przyciąć swój zasięg. To nie jest ogólna n-bitowa metoda liczb losowych, ale zadziała w twoim przypadku i wszystko jest w środku.

Jeśli chcesz być naprawdę słodki i czytać z / dev / urandom, możesz to zrobić:

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

To przeczyta dwa bajty i wydrukuje je jako int bez znaku; nadal musisz zrobić wycinek.

Cascabel
źródło
Użyłem tej techniki i zauważyłem, że od czasu do czasu nie będzie generowanych liczb, po prostu puste miejsce.
PdC
Wymaga zainstalowanego perla. Piszę skrypt, który powinien działać na większości, jeśli nie na wszystkich komputerach z systemem Linux, trzymając się awkwersji z innej odpowiedzi
Lukas Liesis
Dodanie liczb losowych sprzyja środkowym wynikom kosztem niskiej lub wysokiej. Nie jest jednolicie losowy.
izomorfizmy
@ isomorphismes Tak, jeśli dosłownie dodajesz dwie losowe liczby. Ale zakładając, że odwołujesz się do drugiego wyrażenia tutaj, to nie to robi. Jest to liczba losowa w [0,32767] plus niezależny losowy wybór dla następnego bitu, tj. 0 lub 32768. Jest jednolity. (Nie jest to jednak idealne w przypadku pierwotnego pytania, ponieważ musisz przyciąć zakres przy ponownym przewijaniu).
Cascabel
7

Jeśli nie jesteś ekspertem od bashów i chciałeś zamienić to na zmienną w skrypcie bash opartym na Linuksie, spróbuj tego:

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

To daje zakres od 200 do 700 $VARwłącznie.

Berto
źródło
5

Oto kolejny. Myślałem, że zadziała na wszystko, ale losowa opcja sortowania nie jest dostępna na moim polu centos w pracy.

 seq 2000 65000 | sort -R | head -n 1
waladil
źródło
3
sort -Rnie jest również dostępny w systemie OS X.
Lri
5

$RANDOMjest liczbą od 0 do 32767. Chcesz mieć port między 2000 a 65000. Są to 63001 możliwych portów. Jeśli będziemy trzymać się wartości $RANDOM + 2000między 2000 a 33500 , omówimy szereg 31501 portów. Jeśli przerzucimy monetę, a następnie warunkowo dodamy 31501 do wyniku, możemy uzyskać więcej portów, od 33501 do 65001 . Jeśli po prostu upuszczymy 65001, otrzymamy dokładnie wymagany zasięg, z jednolitym rozkładem prawdopodobieństwa dla wszystkich portów, wydaje się.

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
}

Testowanie

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
źródło
5

Możesz to zrobić

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

Jeśli potrzebujesz więcej informacji, zobacz Generator liczb losowych w skrypcie powłoki .

Nowe wyjście
źródło
Prawie. Daje to zakres od 2000 do 67000.
Ogre Psalm33
5

To samo z rubinem:

echo $(ruby -e 'puts rand(20..65)') #=> 65 (inclusive ending)
echo $(ruby -e 'puts rand(20...65)') #=> 37 (exclusive ending)
Lew Łukomsky
źródło
3

Dokumentacja Bash mówi, że przy każdym $RANDOModwołaniu zwracana jest losowa liczba od 0 do 32767. Jeśli zsumujemy dwa kolejne odniesienia, otrzymamy wartości od 0 do 65534, co obejmuje pożądany zakres 63001 możliwości dla liczby losowej między 2000 a 65000.

Aby dostosować go do dokładnego zakresu, używamy sumy modulo 63001, która da nam wartość od 0 do 63000. To z kolei potrzebuje tylko przyrostu o 2000, aby zapewnić pożądaną liczbę losową, między 2000 a 65000. Może to być podsumowane w następujący sposób:

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

Testowanie

# 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

Poprawność obliczeń

Oto pełny, brutalny test poprawności obliczeń. Ten program próbuje po prostu losowo wygenerować wszystkie 63001 różnych możliwości, wykorzystując testowane obliczenia. Ten --jobsparametr powinien przyspieszyć jego działanie, ale nie jest deterministyczny (suma wygenerowanych możliwości może być mniejsza niż 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

Aby ustalić, ile iteracji jest potrzebnych do uzyskania danego prawdopodobieństwa p/qwygenerowania wszystkich 63001 możliwości, uważam, że możemy użyć poniższego wyrażenia. Na przykład tutaj jest obliczenie dla prawdopodobieństwa większego niż 1/2 , a tutaj dla większego niż 9/10 .

Wyrażenie


źródło
1
Jesteś w błędzie. $RANDOMjest liczbą całkowitą . Dzięki „sztuczce” istnieje wiele wartości, które nigdy nie zostaną osiągnięte. -1.
gniourf_gniourf
2
Nie jestem pewien, co masz na myśli przez „jest liczbą całkowitą”, ale poprawny, algorytm był nieprawidłowy. Pomnożenie losowej wartości z ograniczonego zakresu nie zwiększy zakresu. $RANDOMZamiast tego musimy zsumować dwa dostępy i nie przekształcać tego w mnożenie przez dwa, ponieważ $RANDOMma się to zmieniać przy każdym dostępie. Zaktualizowałem odpowiedź o wersję sumaryczną.
6
W ten sposób RANDOM+RANDOMnie uzyskasz jednolitego rozkładu liczb losowych między 0 a 65534.
gniourf_gniourf
3
Prawidłowo, innymi słowy, nie wszystkie kwoty mają równe szanse na wystąpienie. W rzeczywistości jest to pierdnięcie, jeśli sprawdzimy wykres, będzie to piramida! Myślę, że właśnie dlatego otrzymywałem znacznie dłuższe czasy obliczeń niż te oczekiwane w powyższym wzorze. Istnieje również problem z działaniem modulo: sumy od 63001 do (32767 + 32767) podwajają szanse wystąpienia dla pierwszych 2534 portów w porównaniu z resztą portów. Zastanawiałem się nad alternatywami, ale myślę, że lepiej zacząć od zera z nową odpowiedzią, więc głosuję za usunięciem.
4
To jak rzut 2 sześciościennymi kostkami. Statystycznie daje to krzywą dzwonową: niskie prawdopodobieństwo wyrzucenia „2” lub „12”, z najwyższym prawdopodobieństwem uzyskania „7” na środku.
Ogre Psalm33
2

Lub w systemie OS-X następujące funkcje działają dla mnie:

$ gsort --random-sort
Chadwick Boggs
źródło
2

PORT=$(($RANDOM%63000+2001)) myślę, że jest blisko tego, czego chcesz.

PORT=$(($RANDOM$RANDOM$RANDOM%63000+2001))omija ograniczenie rozmiaru, które Cię niepokoi. Ponieważ bash nie rozróżnia między zmienną liczbową a zmienną łańcuchową, działa to doskonale. „Liczba” $RANDOMmoże być łączona jak ciąg, a następnie używana jako liczba w obliczeniach. Niesamowity!

Nicpoń
źródło
1
Rozumiem co mówisz. Zgadzam się, że rozkład będzie inny, ale i tak nie można uzyskać prawdziwej przypadkowości. Czasami lepiej jest użyć $ RANDOM, czasem $ RANDOM $ RANDOM, a czasem $ RANDOM $ RANDOM $ RANDOM, aby uzyskać bardziej równomierną dystrybucję. Więcej $ RANDOM preferuje wyższe numery portów, o ile wiem.
Wastrel
(Usunąłem swój oryginalny komentarz, ponieważ użyłem niewłaściwych wartości liczbowych i było za późno, aby edytować komentarz). Dobrze. x=$(( $n%63000 )jest z grubsza podobny do x=$(( $n % 65535 )); if [ $x -gt 63000 ]; then x=63000.
chepner
Nie zamierzałem krytykować (ani nawet robić) matematyki. Po prostu to zaakceptowałem. To właśnie miałem na myśli: num = ($ RANDOM $ RANDOM $ RANDOM $ RANDOM $ RANDOM $ RANDOM); pick = $ (($ RANDOM% 3)); PORT = $ (($ {num [$ pick]}% 63000 + 2001)) --- wydaje się, że to dużo kłopotów ...
Wastrel
1

Możesz uzyskać losową liczbę poprzez urandom

head -200 /dev/urandom | cksum

Wynik:

3310670062 52870

Aby pobrać jedną część powyższego numeru.

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

Następnie wyjście jest

3310670062

Aby spełnić twoje wymagania,

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

zangw
źródło
0

W ten sposób zwykle generuję liczby losowe. Następnie używam „NUM_1” jako zmiennej dla używanego numeru portu. Oto krótki przykładowy skrypt.

#!/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
Jokai
źródło