Випадкове число з діапазону в сценарії Bash


198

Мені потрібно генерувати випадковий номер порту між 2000-65000сценарієм оболонки. Проблема - $RANDOM15-бітове число, тому я застряг!

PORT=$(($RANDOM%63000+2001)) працювало б чудово, якби не обмеження розміру.

Хтось має приклад того, як я можу це зробити, можливо, витягнувши щось із нього /dev/urandomта отримавши це в межах діапазону?

Відповіді:


398
shuf -i 2000-65000 -n 1

Насолоджуйтесь!

Редагувати : Діапазон включно.


7
Я думаю, що shufце порівняно недавно - я бачив це в системах Ubuntu протягом останніх кількох років, але не зараз RHEL / CentOS.
Каскабель

5
Крім того, для цього використання це, мабуть, добре, але я вважаю, що shufвін фактично перетворює весь вхід. Це робить його поганим вибором, якщо ви генеруєте випадкові числа дуже часто.
Каскабель

3
@Jefromi: У моїй системі, використовуючи цей тест time for i in {1..1000}; do shuf -i 0-$end -n 1000 > /dev/null; doneі порівнявши, end=1було end=65535показано покращення на 25% за коротший діапазон, який склав приблизно 4 секунди різниці за мільйон ітерацій. І це набагато швидше, ніж виконання розрахунку Bash OP в мільйон разів.
Призупинено до подальшого повідомлення.

9
@Денніс Вільямсон: Запуск тесту -n 1показав незначні часові відмінності, навіть якщо end=4000000000. shuf
Приємно

6
у мене немає шуфу на моєму комп'ютері :(
Вірен

79

На Mac OS X та FreeBSD ви також можете використовувати jot:

jot -r 1  2000 65000

5
У цьому прикладі jotє несправедливий розподіл для мінімального та максимального інтервалів (тобто 2000 та 65000). Іншими словами, min і max будуть генеруватися рідше. Детальну інформацію та вирішення див. У моїй відповіді .
Клінт Пахл

jotтакож доступний у більшості дистрибутивів GNU / Linux
Thor,

43

За даними сторінки bash man, $RANDOMвона розподіляється між 0 і 32767; тобто це непідписане 15-бітове значення. Припускаючи $RANDOM, що рівномірно розподілено, ви можете створити рівномірно розподілене 30-бітове ціле число, яке не підписано таким чином:

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

Оскільки ваш діапазон не є потужністю 2, проста операція з модулем дасть вам майже рівномірний розподіл, але з 30-бітовим діапазоном вводу та меншим ніж 16-бітовим діапазоном виходу, як у вашому випадку, це дійсно має бути досить близько:

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

1
змінна $RANDOMне завжди доступна у всіх оболонках. Шукаєте іншого рішення
Лукас Ліесіс

Якщо я правильно це розумію, ви поширюєте 32 000 чисел серед діапазону 1 000 000 000. Але вони потраплять лише на кратні 2 ^ 15 - ви пропускаєте підрахунок на 2 ^ 15, не заповнюючи всі цифри між 1 і 2 ^ 30 рівномірно, що є рівномірним розподілом.
ізоморфізми

@isomorphismes Зверніть увагу, що код посилається $RANDOMдвічі. На оболонках, які підтримують $RANDOM, створюється нове значення щоразу, коли на нього посилаються. Таким чином, цей код заповнює біти від 0 до 14 одним $RANDOMзначенням, а біти 15 - 29 заповнює іншим. Якщо припустити, що $RANDOMце рівномірно та незалежно, воно охоплює всі значення від 0 до 2 ** 30-1, не пропускаючи нічого.
Єсін

41

і ось один з Python

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

і один з awk

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

6
Цей отримує нагороду від мене. Я пишу bash-скрипти для різних систем і вважаю, що awk - це, мабуть, найпоширеніший інструмент для роботи. Працював над mac os x і centos без проблем, і я знаю, що він буде працювати і на моїй debian машині, і, ймовірно, на будь-якій іншій машині нормального ish * nix.
Джон Хант

6
Однак випадкове насіння awk, здається, оновлюється лише один раз / сек, щоб ви могли хотіти: а) уникати будь-якої ціни або б) повторно ініціалізувати насіння.
Джон Хант

+1, тому що це здається єдиною можливістю POSIX без компіляції: RANDOMне гарантується POSIX,
Ciro Santilli 郝海东 冠状 病 六四 事件

Використання -Sпараметра призводить до ImportError: No module named random. Працює, якщо я його видалю. Не впевнений, який задум був для цього.
Кріс Джонсон

1
python -S -c "import random; print random.randrange(2000,63000)"здається, працює добре. Однак, коли я намагаюся отримати випадкове число між 1 і 2, я, здається, завжди отримую 1 ... Думки?
Hubert Léveillé Gauvin

17

Найпростіший загальний спосіб, який спадає на думку, - це однолінійний Perl:

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

Ви завжди можете просто використовувати два числа:

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

Вам все одно доведеться зафіксувати свій діапазон. Це не загальний n-бітний метод випадкових чисел, але він буде працювати у вашому випадку, і все це в bash.

Якщо ви хочете бути справді милою і читати з / dev / urandom, ви можете зробити це:

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

Це прочитає два байти і надрукує їх як безпідписаний int; вам все одно доведеться робити відсікання.


Я використав цю техніку і помітив, що час від часу не буде генеруватися число, просто порожній пробіл.
PdC

Для цього потрібно встановити Perl. Я пишу сценарій, який повинен працювати на більшості, якщо не на всіх машинах Linux, дотримуючись awkверсії з іншої відповіді
Лукас Ліесіс

Додавання випадкових чисел сприяє середнім результатам за рахунок низьких чи високих. Це не рівномірно випадково.
ізоморфізми

@isomorphismes Так, якщо ви буквально просто додаєте два випадкових числа. Але якщо припустити, що ви посилаєтесь на другий вираз тут, це не те, що це робить. Це випадкове число в [0,32767] плюс незалежний випадковий вибір для наступного біта, тобто 0 або 32768. Це рівномірне. (Це не ідеально для початкового питання, хоча, оскільки вам доведеться вирізати діапазон з перепрошиванням.)
Каскабель

7

Якщо ви не баш експерт і хотіли перетворити це на змінну в базі-скрипті на базі Linux, спробуйте:

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

Це дає вам діапазон від 200 до 700 $VARвключно.


5

Ось ще один. Я думав, що це буде працювати практично на що завгодно, але випадковий варіант сортування недоступний у моїй центровій коробці на роботі.

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

3
sort -Rнедоступний і в OS X.
Лрі

5

$RANDOMце число від 0 до 32767. Ви хочете, щоб порт знаходився між 2000 і 65000. Це 63001 можливих портів. Якщо ми будемо дотримуватися значень $RANDOM + 2000між 2000 і 33500 , ми розглянемо ряд 31501 портів. Якщо ми перевернемо монету і потім умовно додамо до результату 31501 , ми можемо отримати більше портів, від 33501 до 65001 . Тоді якщо ми просто скинемо 65001, ми отримаємо потрібне точне покриття, з рівномірним розподілом ймовірності для всіх портів.

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
}

Тестування

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



3

Документація Баша говорить, що кожного разу, коли $RANDOMпосилається, повертається випадкове число між 0 і 32767. Якщо підсумовувати дві послідовні посилання, ми отримуємо значення від 0 до 65534, що охоплює бажаний діапазон 63001 можливостей для випадкового числа між 2000 і 65000.

Щоб пристосувати його до точного діапазону, ми використовуємо суму модуля 63001, що дасть нам значення від 0 до 63000. Це, в свою чергу, просто збільшується до 2000 року, щоб забезпечити бажане випадкове число, між 2000 і 65000. Це може бути підсумовується так:

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

Тестування

# 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

Правильність розрахунку

Ось повний тест на грубі сили на правильність розрахунку. Ця програма просто намагається генерувати всі 63001 різні можливості випадковим чином, використовуючи перевірений розрахунок. --jobsПараметр повинен зробити його працювати швидше, але це не детермінованим (всього можливостей згенерованих може бути нижче , ніж 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

Для визначення кількості ітерацій, необхідних для отримання заданої ймовірності p/qвсіх сформованих 63001 можливостей, я вважаю, що ми можемо використовувати вираз нижче. Наприклад, ось розрахунок для ймовірності, що перевищує 1/2 , а тут більше 9/10 .

Вираз


1
Ви помиляєтеся. $RANDOM- ціле число . З вашим "фокусом" є багато цінностей, яких ніколи не буде досягнуто. -1.
gniourf_gniourf

2
Я не впевнений, що ви маєте на увазі під "цілим числом", але правильно, алгоритм був неправильним. Множення випадкового значення з обмеженого діапазону не збільшить діапазон. Нам потрібно підсумовувати два доступу до $RANDOMцього, а не перетворювати це на множення на два, оскільки $RANDOMце має змінюватися при кожному доступі. Я оновив відповідь версією суми.

6
Це RANDOM+RANDOMне дасть вам рівномірного розподілу випадкових чисел між 0 і 65534.
gniourf_gniourf

3
Правильно, іншими словами, не всі суми мають однакові шанси. Насправді це далеко не те, якщо ми перевіримо графік - це піраміда! Я думаю, саме тому я отримую значно більший час розрахунку, ніж очікувані формулою вище. Існує також проблема з модульною операцією: суми від 63001 до (32767 + 32767) вдвічі більше шансів появи для перших 2534 портів порівняно з рештою портів. Я думав про альтернативи, але думаю, що краще просто почати з нуля нову відповідь, тому я голосую за видалення.

4
Це як закатати 2 шестигранні кістки. Статистично це дає криву дзвону: низька ймовірність прокатки "2" або "12", з найбільшою ймовірністю отримання "7" посередині.
Псалом Огре3333


2

PORT=$(($RANDOM%63000+2001)) є близьким до того, що ти хочеш, я думаю.

PORT=$(($RANDOM$RANDOM$RANDOM%63000+2001))обходить обмеження розміру, яке вас непокоїть. Оскільки bash не робить різниці між числовою змінною та рядковою змінною, це працює чудово. "Число" $RANDOMможна об'єднати як рядок, а потім використовувати як число в обчисленні. Дивовижний!


1
Я бачу, що ти кажеш. Я погоджуюсь, що розподіл буде різним, але реальної випадковості все одно ви не можете отримати. Можливо, краще іноді використовувати $ RANDOM, іноді $ RANDOM $ RANDOM, а іноді $ RANDOM $ RANDOM $ RANDOM, щоб отримати більш рівномірний розподіл. Наскільки я можу сказати, більше $ RANDOMs надає перевагу більшим номерам портів.
Wastrel

(Я видалив свій оригінальний коментар, оскільки я використав деякі неправильні числові значення, і було занадто пізно для редагування коментаря). Правильно. x=$(( $n%63000 )приблизно схожий на x=$(( $n % 65535 )); if [ $x -gt 63000 ]; then x=63000.
чепнер

Я не збирався критикувати (або навіть робити) математику. Я просто прийняв це. Це те, що я мав на увазі: num = ($ RANDOM $ RANDOM $ RANDOM $ RANDOM $ RANDOM $ RANDOM); pick = $ (($ RANDOM% 3)); PORT = $ (($ {num [$ pick]}% 63000 + 2001)) --- це, здається, багато клопоту ...
Wastrel

1

Ви можете отримати випадкове число urandom

head -200 /dev/urandom | cksum

Вихід:

3310670062 52870

Щоб отримати одну частину вищевказаного числа.

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

Тоді вихід є

3310670062

Щоб відповідати вашим вимогам,

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


0

Ось як я зазвичай генерую випадкові числа. Тоді я використовую "NUM_1" як змінну для номера порту, який я використовую. Ось короткий приклад сценарію.

#!/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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.