Случайное число из диапазона в скрипте Bash


198

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

PORT=$(($RANDOM%63000+2001)) будет работать хорошо, если бы не ограничение размера.

У кого-нибудь есть пример того, как я могу сделать это, возможно, извлекая что-то из этого /dev/urandomи получая это в пределах диапазона?

Ответы:


399
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 секунды на миллион итераций. И это много быстрее , чем выполнение расчета Баш OP в миллион раз.
— Приостановлено до дальнейшего уведомления.

9
@Dennis Williamson: Проведение теста -n 1показало незначительную разницу во времени, даже с end=4000000000. Полезно знать, shufработает умно, а не усердно :-)
— leedm777

6
у меня нет маф на моем Mac :(
— Viren

79

В Mac OS X и FreeBSD вы также можете использовать jot:

jot -r 1  2000 65000

5
В этом примере jotимеет несправедливое распределение для минимума и максимума интервала (например, 2000 и 65000). Другими словами, min и max будут генерироваться реже. Смотрите мой краткий ответ для деталей и обходного пути.
— Клинт Пахл

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

43

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

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

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

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

1
переменная $RANDOMне всегда доступна во всех оболочках. В поисках другого решения
— Lukas Liesis

Если я правильно понимаю, вы распространяете 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 ... Мысли?
— Юбер Левей Говен

17

Самый простой общий способ, который приходит на ум - это Perl One-Liner:

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

Это прочитает два байта и напечатает их как беззнаковое целое; Вы все еще должны сделать свою вырезку.


Я использовал эту технику и заметил, что время от времени не будет генерироваться число, просто пробел.
— PdC

Требуется установленный Perl. Я пишу сценарий, который должен работать на большинстве, если не на всех машинах Linux, придерживаясь awkверсии из другого ответа
— Lukas Liesis

Добавление случайных чисел благоприятствует средним результатам за счет низкого или высокого. Это не случайно случайно.
— изоморфизм

@isomorphismes Да, если вы буквально просто добавляете два случайных числа. Но, если вы ссылаетесь на второе выражение здесь, это не то, что он делает. Это случайное число в [0,32767] плюс независимый случайный выбор для следующего бита, т. Е. 0 или 32768. Оно равномерно. (Это не идеальный вариант для первоначального вопроса, так как вы должны обрезать диапазон с помощью перестановки.)
— Каскабель

7

Если вы не являетесь экспертом по bash и хотели получить это в виде переменной в сценарии bash на основе 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
— Lri

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


5

То же самое с рубином:

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

3

В документации Bash говорится, что при каждой $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.

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

4
Это как бросить 2 шестигранных кубика. Статистически это дает кривую колокольчика: низкая вероятность выпадения «2» или «12», с наибольшей вероятностью получения «7» в середине.
— Огрский псалом33


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.
— chepner

Я не собирался критиковать (или даже делать) математику. Я просто принял это. Вот что я имел в виду: num = ($ RANDOM $ RANDOM $ RANDOM $ RANDOM $ RANDOM $ 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.