Nomor acak dari rentang dalam Bash Script

196

Saya perlu membuat nomor port acak antara 2000-65000dari skrip shell. Masalahnya adalah $RANDOMangka 15-bit, jadi saya mandek!

PORT=$(($RANDOM%63000+2001)) akan bekerja dengan baik jika bukan karena batasan ukuran.

Adakah yang punya contoh bagaimana saya bisa melakukan ini, mungkin dengan mengekstraksi sesuatu dari /dev/urandomdan mendapatkannya dalam jangkauan?

Jason Gooner
sumber

Jawaban:

398
shuf -i 2000-65000 -n 1

Nikmati!

Sunting : Rentang ini inklusif.

leedm777
sumber
7
Saya pikir shufini relatif baru - saya telah melihatnya di sistem Ubuntu dalam beberapa tahun terakhir tetapi tidak RHEL / CentOS saat ini.
Cascabel
5
Juga, mungkin baik-baik saja untuk penggunaan ini, tapi saya percaya shufbenar-benar mengubah seluruh input. Ini membuatnya menjadi pilihan yang buruk jika Anda sering membuat angka acak.
Cascabel
3
@ Jeffromi: Pada sistem saya, menggunakan tes ini time for i in {1..1000}; do shuf -i 0-$end -n 1000 > /dev/null; donedan membandingkan end=1untuk end=65535menunjukkan peningkatan sekitar 25% untuk rentang yang lebih pendek yang berjumlah sekitar 4 detik perbedaan lebih dari sejuta iterasi. Dan itu banyak lebih cepat daripada melakukan OP perhitungan Bash satu juta kali.
Dijeda sampai pemberitahuan lebih lanjut.
8
@Dennis Williamson: Menjalankan tes Anda dengan -n 1menunjukkan perbedaan waktu yang dapat diabaikan, bahkan dengan end=4000000000. Baik untuk mengetahui shufbekerja dengan cerdas, tidak sulit :-)
leedm777
6
saya tidak punya shuf di mac saya :(
Viren
79

Pada Mac OS X dan FreeBSD Anda juga dapat menggunakan jot:

jot -r 1  2000 65000
errno
sumber
5
Dalam contoh ini, jotmemiliki distribusi yang tidak adil untuk interval minimum dan maksimum (yaitu, 2000 dan 65000). Dengan kata lain, min dan max akan dihasilkan lebih jarang. Lihat jawaban saya untuk detail dan solusinya.
Clint Pachl
jotjuga tersedia di sebagian besar distribusi GNU / Linux
Thor
43

Menurut halaman manual bash, $RANDOMdidistribusikan antara 0 dan 32767; yaitu, nilai 15-bit yang tidak ditandatangani. Dengan asumsi $RANDOMterdistribusi secara merata, Anda dapat membuat integer 30-bit unsigned yang terdistribusi secara merata sebagai berikut:

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

Karena jangkauan Anda bukan kekuatan 2, operasi modulo sederhana hanya akan hampir memberi Anda distribusi yang seragam, tetapi dengan rentang input 30-bit dan rentang output kurang dari 16-bit, seperti yang Anda miliki dalam kasus Anda, ini harus benar-benar cukup dekat:

PORT=$(( ((RANDOM<<15)|RANDOM) % 63001 + 2000 ))
Jesin
sumber
1
variabel $RANDOMtidak selalu tersedia di semua shell. Mencari solusi lain
Lukas Liesis
Jika saya memahami ini dengan benar, Anda menyebarkan 32.000 angka di tengah kisaran 1.000.000.000. Tetapi mereka hanya akan mencapai kelipatan 2 ^ 15 — Anda melompati 2 ^ 15, tidak mengisi semua angka antara 1 dan 2 ^ 30 secara merata, yang merupakan distribusi yang seragam.
isomorfisma
@ isomorfisma Perhatikan bahwa kode referensi $RANDOMdua kali. Pada shell yang mendukung $RANDOM, nilai baru dihasilkan setiap kali direferensikan. Jadi kode ini mengisi bit 0 hingga 14 dengan satu $RANDOMnilai & mengisi bit 15 hingga 29 dengan yang lain. Dengan asumsi $RANDOMseragam & independen, ini mencakup semua nilai dari 0 hingga 2 ** 30-1 tanpa melewatkan apa pun.
Jesin
41

dan inilah satu dengan Python

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

dan satu dengan awk

awk 'BEGIN{srand();print int(rand()*(63000-2000))+2000 }'
ghostdog74
sumber
6
Yang ini mendapat dukungan dari saya. Saya menulis skrip bash untuk berbagai sistem dan saya percaya awk mungkin adalah alat paling banyak untuk pekerjaan itu. Bekerja pada mac os x dan centos tanpa masalah dan saya tahu itu akan bekerja pada mesin debian saya juga, dan mungkin mesin normal-ish * nix lainnya.
John Hunt
6
Namun, benih acak awk hanya tampak menyegarkan sekali / detik sehingga Anda mungkin ingin a) menghindari di semua biaya atau b) menginisialisasi ulang benih.
John Hunt
1 karena ini tampaknya menjadi satu-satunya kemungkinan POSIX tanpa kompilasi: RANDOMtidak dijamin oleh POSIX,
Ciro Santilli郝海东冠状病六四事件法轮功
Menggunakan -Sopsi menghasilkan ImportError: No module named random. Bekerja jika saya menghapusnya. Tidak yakin apa niat ghostdog untuk itu.
Chris Johnson
1
python -S -c "import random; print random.randrange(2000,63000)"tampaknya berfungsi dengan baik. Namun, ketika saya mencoba untuk mendapatkan angka acak antara 1 dan 2, saya sepertinya selalu mendapatkan 1 ... Pikiran?
Hubert Léveillé Gauvin
17

Cara umum paling sederhana yang terlintas dalam pikiran adalah perl one-liner:

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

Anda selalu bisa menggunakan dua angka:

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

Anda masih harus klip ke rentang Anda. Ini bukan metode angka acak n-bit umum, tetapi ini akan bekerja untuk kasus Anda, dan semuanya ada di dalam bash.

Jika Anda ingin benar-benar lucu dan membaca dari / dev / urandom, Anda bisa melakukan ini:

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

Itu akan membaca dua byte dan mencetaknya sebagai int tanpa tanda; Anda masih harus melakukan kliping.

Cascabel
sumber
Saya menggunakan teknik ini dan perhatikan bahwa sekarang dan kemudian tidak akan ada angka yang dihasilkan, hanya ruang kosong.
PdC
Itu membutuhkan perl diinstal. Saya menulis sebuah skrip yang harus dijalankan pada kebanyakan jika tidak semua mesin linux, tetap dengan awkversi dari jawaban lain
Lukas Liesis
Menambahkan angka acak mendukung hasil tengah dengan mengorbankan rendah atau tinggi. Itu tidak acak acak.
isomorfisma
@ isomorfisma Ya, jika Anda benar-benar hanya menambahkan dua angka acak. Tetapi, dengan asumsi Anda merujuk pada ungkapan kedua di sini, bukan itu yang dilakukannya. Ini adalah angka acak dalam [0,32767] ditambah pilihan acak independen untuk bit berikutnya, yaitu 0 atau 32768. Ini seragam. (Ini tidak ideal untuk pertanyaan awal karena Anda harus memotong rentang dengan rerolling.)
Cascabel
7

Jika Anda bukan pakar bash dan ingin memasukkan ini ke variabel dalam skrip bash berbasis Linux, coba ini:

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

Itu membuat Anda kisaran 200 hingga 700 ke $VAR, inklusif.

Berto
sumber
5

Ini satu lagi. Saya pikir itu akan bekerja pada apa saja, tetapi opsi acak sort tidak tersedia pada kotak centos saya di tempat kerja.

 seq 2000 65000 | sort -R | head -n 1
Valadil
sumber
3
sort -Rjuga tidak tersedia di OS X.
Lri
5

$RANDOMadalah angka antara 0 dan 32767. Anda menginginkan port antara 2000 dan 65000. Ini adalah kemungkinan 63001 port. Jika kita tetap berpegang pada nilai $RANDOM + 2000antara 2000 dan 33500 , kita mencakup kisaran 31501 port. Jika kita melempar koin dan menambahkan 31501 secara kondisional ke hasilnya, kita bisa mendapatkan lebih banyak port, dari 33501 ke 65001 . Kemudian jika kita hanya menjatuhkan 65001, kita mendapatkan cakupan tepat yang dibutuhkan, dengan distribusi probabilitas yang seragam untuk semua port, tampaknya.

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
}

Pengujian

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
sumber
5

Kamu bisa melakukan ini

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

Jika Anda memerlukan detail lebih lanjut, lihat Pembuat Angka Acak Shell Script .

Keluar Baru
sumber
Hampir. Ini memberi Anda kisaran 2000 hingga 67000.
Ogre Psalm33
5

Sama dengan ruby:

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

Dokumentasi Bash mengatakan bahwa setiap kali $RANDOMdireferensikan, angka acak antara 0 dan 32767 dikembalikan. Jika kami menjumlahkan dua referensi berturut-turut, kami mendapatkan nilai dari 0 hingga 65534, yang mencakup kisaran 63001 kemungkinan yang diinginkan untuk angka acak antara 2000 dan 65000.

Untuk menyesuaikannya dengan rentang yang tepat, kami menggunakan jumlah modulo 63001, yang akan memberi kami nilai dari 0 hingga 63000. Ini pada gilirannya hanya membutuhkan kenaikan pada tahun 2000 untuk memberikan angka acak yang diinginkan, antara 2000 dan 65000. Ini bisa menjadi diringkas sebagai berikut:

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

Pengujian

# 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

Ketepatan perhitungan

Berikut ini adalah uji penuh, brute-force untuk kebenaran perhitungan. Program ini hanya mencoba untuk menghasilkan semua 63001 kemungkinan berbeda secara acak, menggunakan perhitungan yang sedang diuji. The --jobsparameter harus membuatnya berjalan lebih cepat, tapi tidak deterministik (total kemungkinan dihasilkan mungkin lebih rendah dari 63.001).

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

Untuk menentukan berapa banyak iterasi yang diperlukan untuk mendapatkan probabilitas tertentu p/qdari semua 63001 kemungkinan yang telah dihasilkan, saya percaya kita bisa menggunakan ungkapan di bawah ini. Sebagai contoh, di sini adalah perhitungan untuk probabilitas lebih besar dari 1/2 , dan di sini untuk lebih dari 9/10 .

Ekspresi


sumber
1
Anda salah. $RANDOMadalah bilangan bulat . Dengan "trik" Anda, ada banyak nilai yang tidak akan pernah tercapai. -1.
gniourf_gniourf
2
Saya tidak yakin apa yang Anda maksud dengan "adalah bilangan bulat", tetapi benar, algoritme itu salah. Mengalikan nilai acak dari rentang terbatas tidak akan meningkatkan rentang. Kita perlu menjumlahkan dua akses $RANDOM, dan jangan $RANDOMmengubah itu menjadi perkalian dua, karena seharusnya berubah pada setiap akses. Saya telah memperbarui jawabannya dengan versi penjumlahan.
6
Melakukan RANDOM+RANDOMtidak akan memberi Anda distribusi seragam angka acak antara 0 dan 65534.
gniourf_gniourf
3
Benar, dengan kata lain tidak semua jumlah memiliki peluang yang sama untuk terjadi. Bahkan, kentut dari itu, jika kita periksa grafik itu piramida! Saya pikir inilah mengapa saya mendapatkan waktu kalkulasi yang jauh lebih besar daripada yang diharapkan oleh rumus di atas. Ada juga masalah dengan operasi modulo: jumlah dari 63001 ke (32767 + 32767) menggandakan peluang terjadinya untuk 2534 port pertama dibandingkan dengan sisa port. Saya telah memikirkan alternatif, tetapi saya pikir lebih baik mulai dari awal dengan jawaban baru, jadi saya memilih yang ini untuk dihapus.
4
Ini seperti menggulung 2 dadu enam sisi. Secara statistik ini memberi Anda kurva lonceng: probabilitas rendah menggulirkan "2" atau "12", dengan probabilitas tertinggi untuk mendapatkan "7" di tengah.
Ogre Psalm33
2

Atau di OS-X berikut ini berfungsi untuk saya:

$ gsort --random-sort
Chadwick Boggs
sumber
2

PORT=$(($RANDOM%63000+2001)) dekat dengan apa yang Anda inginkan saya pikir.

PORT=$(($RANDOM$RANDOM$RANDOM%63000+2001))mengatasi batasan ukuran yang mengganggu Anda. Karena bash tidak membuat perbedaan antara variabel angka dan variabel string, ini berfungsi dengan baik. "Angka" $RANDOMdapat digabungkan seperti string, dan kemudian digunakan sebagai angka dalam perhitungan. Luar biasa!

Pemboros
sumber
1
Saya mengerti apa yang Anda katakan. Saya setuju distribusinya akan berbeda, tetapi Anda tidak bisa mendapatkan keacakan nyata. Mungkin lebih baik untuk kadang-kadang menggunakan $ ACAK, kadang-kadang $ ACAK $ ACAK, dan kadang-kadang $ ACAK $ ACAK $ ACAK untuk mendapatkan distribusi yang lebih merata. Lebih banyak $ ACAK mendukung nomor port yang lebih tinggi, sejauh yang saya tahu.
Wastrel
(Saya menghapus komentar asli saya, karena saya menggunakan beberapa nilai numerik yang salah dan sudah terlambat untuk mengedit komentar). Baik. x=$(( $n%63000 )kira-kira mirip dengan x=$(( $n % 65535 )); if [ $x -gt 63000 ]; then x=63000.
chepner
Saya tidak akan mengkritik (atau bahkan melakukan) matematika. Saya hanya menerimanya. Inilah yang saya maksud: num = ($ RANDOM $ RANDOM $ RANDOM $ RANDOM $ RANDOM $ RANDOM); pick = $ (($ RANDOM% 3)); PORT = $ (($ {num [$ pick]}% 63000 + 2001)) --- yang sepertinya banyak masalah ...
Wastrel
1

Anda bisa mendapatkan nomor acak urandom

head -200 /dev/urandom | cksum

Keluaran:

3310670062 52870

Untuk mengambil satu bagian dari nomor di atas.

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

Maka outputnya adalah

3310670062

Untuk memenuhi kebutuhan Anda,

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

zangw
sumber
0

Beginilah biasanya saya menghasilkan angka acak. Lalu saya menggunakan "NUM_1" sebagai variabel untuk nomor port yang saya gunakan. Berikut ini skrip contoh singkat.

#!/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
sumber