Zufallszahl aus einem Bereich in einem Bash-Skript


195

Ich muss eine zufällige Portnummer zwischen 2000-65000aus einem Shell-Skript generieren . Das Problem ist $RANDOMeine 15-Bit-Zahl, also stecke ich fest!

PORT=$(($RANDOM%63000+2001)) würde gut funktionieren, wenn es nicht für die Größenbeschränkung wäre.

Hat jemand ein Beispiel dafür, wie ich das tun kann, vielleicht indem ich etwas extrahiere /dev/urandomund es in einen Bereich bringe?

Antworten:


396
shuf -i 2000-65000 -n 1

Genießen!

Bearbeiten : Der Bereich ist inklusive.


7
Ich denke, es shufist relativ neu - ich habe es in den letzten Jahren auf Ubuntu-Systemen gesehen, aber nicht auf dem aktuellen RHEL / CentOS.
Cascabel

4
Auch ist es wahrscheinlich in Ordnung für diese Verwendung, aber ich glaube, dass shufes tatsächlich die gesamte Eingabe permutiert. Dies macht es zu einer schlechten Wahl, wenn Sie die Zufallszahlen sehr häufig generieren.
Cascabel

2
@Jefromi: Auf meinem System zeigte die Verwendung dieses Tests time for i in {1..1000}; do shuf -i 0-$end -n 1000 > /dev/null; doneund der Vergleich end=1mit end=65535eine Verbesserung um 25% für den kürzeren Bereich, der sich auf eine Differenz von ungefähr 4 Sekunden über eine Million Iterationen belief. Und es ist viel schneller, als die Bash-Berechnung des OP millionenfach durchzuführen.
Bis auf weiteres angehalten.

7
@ Tennis Williamson: Das Ausführen Ihres Tests mit -n 1zeigte vernachlässigbare Zeitunterschiede, auch mit end=4000000000. Gut zu wissen, shuffunktioniert klug, nicht schwer :-)
leedm777

6
Ich habe nicht shuf auf meinem Mac :(
Viren

79

Unter Mac OS X und FreeBSD können Sie auch jot verwenden:

jot -r 1  2000 65000

5
In diesem Beispiel jotist die Verteilung für das Minimum und Maximum des Intervalls ungerecht (dh 2000 und 65000). Mit anderen Worten, Min und Max werden weniger häufig generiert. Weitere Informationen und eine Problemumgehung finden Sie in meiner Antwort .
Clint Pachl

jotist auch in den meisten GNU / Linux-Distributionen verfügbar
Thor

42

Laut Bash-Manpage $RANDOMist zwischen 0 und 32767 verteilt; Das heißt, es ist ein vorzeichenloser 15-Bit-Wert. Unter der Annahme, dass die $RANDOMVerteilung gleichmäßig ist, können Sie eine gleichmäßig verteilte vorzeichenlose 30-Bit-Ganzzahl wie folgt erstellen:

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

Da Ihr Bereich keine Potenz von 2 ist, erhalten Sie mit einer einfachen Modulo-Operation nur eine nahezu gleichmäßige Verteilung, jedoch mit einem 30-Bit-Eingangsbereich und einem weniger als 16-Bit-Ausgabebereich, wie Sie es in Ihrem Fall getan haben. das sollte wirklich nah genug sein:

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

1
Variable $RANDOMist nicht immer in allen Shells verfügbar. Auf der Suche nach einer anderen Lösung
Lukas Liesis

Wenn ich das richtig verstehe, verbreiten Sie 32.000 Zahlen in einem Bereich von 1.000.000.000. Aber sie treffen nur auf Vielfache von 2 ^ 15 - Sie überspringen das Zählen um 2 ^ 15 und füllen nicht alle Ziffern zwischen 1 und 2 ^ 30 gleichmäßig aus, was eine gleichmäßige Verteilung ist.
Isomorphismen

@isomorphismes Beachten Sie, dass der Code $RANDOMzweimal verweist . Bei Shells, die dies unterstützen $RANDOM, wird bei jedem Verweis ein neuer Wert generiert. Dieser Code füllt also die Bits 0 bis 14 mit einem $RANDOMWert und die Bits 15 bis 29 mit einem anderen. Vorausgesetzt, es $RANDOMist einheitlich und unabhängig, deckt dies alle Werte von 0 bis 2 ** 30-1 ab, ohne etwas zu überspringen.
Jesin

41

und hier ist einer mit Python

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

und einer mit awk

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

6
Dieser bekommt eine Gegenstimme von mir. Ich schreibe Bash-Skripte für verschiedene Systeme und glaube, dass awk wahrscheinlich das am häufigsten verwendete Werkzeug für diesen Job ist. Arbeitete ohne Probleme auf Mac OS X und Centos und ich weiß, dass es auch auf meinem Debian-Computer und wahrscheinlich auf jedem anderen normalen * nix-Computer funktioniert.
John Hunt

6
Der zufällige Startwert von awk scheint jedoch nur einmal pro Sekunde aktualisiert zu werden. Sie sollten daher a) um jeden Preis vermeiden oder b) den Startwert neu initialisieren.
John Hunt

+1, weil dies die einzige POSIX-Möglichkeit ohne Kompilierung zu sein scheint: RANDOMwird von POSIX nicht garantiert,
Ciro Santilli 法轮功 冠状 病 六四 六四 28

Die Verwendung der -SOption führt zu ImportError: No module named random. Funktioniert, wenn ich das entferne. Ich bin mir nicht sicher, was Ghostdog dafür vorhatte.
Chris Johnson

1
python -S -c "import random; print random.randrange(2000,63000)"scheint gut zu funktionieren. Wenn ich jedoch versuche, eine Zufallszahl zwischen 1 und 2 zu erhalten, bekomme ich anscheinend immer 1 ... Gedanken?
Hubert Léveillé Gauvin

17

Der einfachste allgemeine Weg, der mir in den Sinn kommt, ist ein Perl-Einzeiler:

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

Sie können immer nur zwei Zahlen verwenden:

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

Sie müssen immer noch an Ihrem Bereich befestigen. Es ist keine allgemeine n-Bit-Zufallszahlenmethode, aber sie funktioniert für Ihren Fall und ist alles in Bash.

Wenn Sie wirklich süß sein und aus / dev / urandom lesen möchten, können Sie dies tun:

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

Das liest zwei Bytes und druckt sie als vorzeichenloses int; Sie müssen noch Ihren Ausschnitt machen.


Ich habe diese Technik angewendet und festgestellt, dass ab und zu keine Nummer generiert wird, sondern nur ein Leerzeichen.
PdC

Es erfordert Perl installiert. Ich schreibe ein Skript, das auf den meisten, wenn nicht allen Linux-Maschinen ausgeführt werden sollte, wobei ich mich an die awkVersion einer anderen Antwort
halte

Das Hinzufügen von Zufallszahlen begünstigt mittlere Ergebnisse auf Kosten von niedrig oder hoch. Es ist nicht einheitlich zufällig.
Isomorphismen

@isomorphismes Ja, wenn Sie buchstäblich nur zwei Zufallszahlen hinzufügen. Angenommen, Sie beziehen sich hier auf den zweiten Ausdruck, dann ist das nicht das, was es tut. Es ist eine Zufallszahl in [0,32767] plus eine unabhängige Zufallsauswahl für das nächste Bit, dh 0 oder 32768. Es ist einheitlich. (Es ist jedoch nicht ideal für die ursprüngliche Frage, da Sie den Bereich mit erneutem Rollen abschneiden müssen.)
Cascabel

7

Wenn Sie kein Bash-Experte sind und dies in eine Variable in einem Linux-basierten Bash-Skript umwandeln möchten, versuchen Sie Folgendes:

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

Damit haben Sie einen Bereich von 200 bis $VAReinschließlich 700 .


5

Hier ist ein anderes. Ich dachte, es würde bei fast allem funktionieren, aber die zufällige Option von sort ist auf meiner Centos-Box bei der Arbeit nicht verfügbar.

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

3
sort -Rist auch unter OS X nicht verfügbar.
Lri

5

$RANDOMist eine Zahl zwischen 0 und 32767. Sie möchten einen Port zwischen 2000 und 65000. Dies sind 63001 mögliche Ports. Wenn wir uns an Werte $RANDOM + 2000zwischen 2000 und 33500 halten , decken wir einen Bereich von 31501 Ports ab. Wenn wir eine Münze werfen und dann bedingt 31501 zum Ergebnis hinzufügen, können wir mehr Ports von 33501 bis 65001 erhalten . Wenn wir dann nur 65001 fallen lassen, erhalten wir genau die erforderliche Abdeckung mit einer einheitlichen Wahrscheinlichkeitsverteilung für alle Ports, wie es scheint.

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
}

Testen

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

Gleiches gilt für Rubin:

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

3

In der Bash-Dokumentation heißt es, dass bei jedem $RANDOMVerweis eine Zufallszahl zwischen 0 und 32767 zurückgegeben wird. Wenn wir zwei aufeinanderfolgende Referenzen summieren, erhalten wir Werte von 0 bis 65534, was den gewünschten Bereich von 63001 Möglichkeiten für eine Zufallszahl zwischen 2000 und 65000 abdeckt.

Um es auf den genauen Bereich einzustellen, verwenden wir die Summe modulo 63001, die einen Wert von 0 bis 63000 ergibt. Dies erfordert wiederum nur ein Inkrement um 2000, um die gewünschte Zufallszahl zwischen 2000 und 65000 bereitzustellen. Dies kann sein wie folgt zusammengefasst:

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

Testen

# 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

Richtigkeit der Berechnung

Hier ist ein vollständiger Brute-Force-Test für die Richtigkeit der Berechnung. Dieses Programm versucht nur, alle 63001 verschiedenen Möglichkeiten zufällig unter Verwendung der zu testenden Berechnung zu generieren. Der --jobsParameter sollte die Ausführung beschleunigen, ist jedoch nicht deterministisch (die Gesamtzahl der generierten Möglichkeiten kann unter 63001 liegen).

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

Um zu bestimmen, wie viele Iterationen erforderlich sind, um eine bestimmte Wahrscheinlichkeit p/qzu erhalten, dass alle 63001-Möglichkeiten generiert wurden, können wir meines Erachtens den folgenden Ausdruck verwenden. Hier ist zum Beispiel die Berechnung für eine Wahrscheinlichkeit größer als 1/2 und hier für mehr als 9/10 .

Ausdruck


1
Du liegst falsch. $RANDOMist eine ganze Zahl . Mit Ihrem "Trick" gibt es viele Werte, die niemals erreicht werden. -1.
gniourf_gniourf

2
Ich bin nicht sicher, was Sie mit "ist eine ganze Zahl" meinen, aber richtig, der Algorithmus war falsch. Durch Multiplizieren eines zufälligen Werts aus einem begrenzten Bereich wird der Bereich nicht vergrößert. Wir müssen $RANDOMstattdessen zwei Zugriffe auf summieren und dies nicht in eine Multiplikation mit zwei umgestalten, da $RANDOMsich dies bei jedem Zugriff ändern soll. Ich habe die Antwort mit der Summenversion aktualisiert.

6
Wenn RANDOM+RANDOMSie dies tun , erhalten Sie keine gleichmäßige Verteilung der Zufallszahlen zwischen 0 und 65534.
gniourf_gniourf

3
Richtig, mit anderen Worten, nicht alle Beträge haben die gleiche Chance. Tatsächlich ist es ein Furz davon, wenn wir das Diagramm überprüfen, ist es eine Pyramide! Ich denke, aus diesem Grund habe ich erheblich längere Berechnungszeiten als in der obigen Formel erwartet. Es gibt auch ein Problem mit der Modulo-Operation: Die Summen von 63001 bis (32767 + 32767) verdoppeln die Wahrscheinlichkeit des Auftretens für die ersten 2534 Ports im Vergleich zu den übrigen Ports. Ich habe über Alternativen nachgedacht, aber ich denke, es ist besser, mit einer neuen Antwort von vorne zu beginnen, also stimme ich diese für die Löschung.

4
Es ist, als würde man zwei sechsseitige Würfel werfen. Statistisch gesehen erhalten Sie eine Glockenkurve: geringe Wahrscheinlichkeit, eine "2" oder "12" zu würfeln, mit der höchsten Wahrscheinlichkeit, eine "7" in der Mitte zu erhalten.
Oger Psalm33


2

PORT=$(($RANDOM%63000+2001)) ist nah an dem, was du willst, denke ich.

PORT=$(($RANDOM$RANDOM$RANDOM%63000+2001))umgeht die Größenbeschränkung, die Sie stört. Da bash nicht zwischen einer Zahlenvariablen und einer Zeichenfolgenvariablen unterscheidet, funktioniert dies einwandfrei. Die "Zahl" $RANDOMkann wie eine Zeichenfolge verkettet und dann als Zahl in einer Berechnung verwendet werden. Tolle!


1
Ich verstehe, was du sagst. Ich bin damit einverstanden, dass die Verteilung anders sein wird, aber Sie können sowieso keine echte Zufälligkeit bekommen. Es ist möglicherweise besser, manchmal $ RANDOM, manchmal $ RANDOM $ RANDOM und manchmal $ RANDOM $ RANDOM $ RANDOM zu verwenden, um eine gleichmäßigere Verteilung zu erzielen. Mehr $ RANDOMs begünstigen, soweit ich das beurteilen kann, höhere Portnummern.
Wastrel

(Ich habe meinen ursprünglichen Kommentar gelöscht, da ich einige falsche Zahlenwerte verwendet habe und es zu spät war, den Kommentar zu bearbeiten.) Richtig. x=$(( $n%63000 )ist ungefähr ähnlich zu x=$(( $n % 65535 )); if [ $x -gt 63000 ]; then x=63000.
Chepner

Ich würde die Mathematik nicht kritisieren (oder sogar tun). Ich habe es einfach akzeptiert. Das habe ich gemeint: num = ($ RANDOM $ RANDOM $ RANDOM $ RANDOM $ RANDOM $ RANDOM); pick = $ (($ RANDOM% 3)); PORT = $ (($ {num [$ pick]}% 63000 + 2001)) --- das scheint eine Menge Ärger zu sein ...
Wastrel

1

Sie können die Zufallszahl durch bekommen urandom

head -200 /dev/urandom | cksum

Ausgabe:

3310670062 52870

Um den einen Teil der obigen Nummer abzurufen.

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

Dann ist die Ausgabe

3310670062

Um Ihre Anforderungen zu erfüllen,

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


0

So generiere ich normalerweise Zufallszahlen. Dann verwende ich "NUM_1" als Variable für die von mir verwendete Portnummer. Hier ist ein kurzes Beispielskript.

#!/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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.