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

198

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

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

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

Джейсон Гунер
источник

Ответы:

399
shuf -i 2000-65000 -n 1

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

Редактировать : диапазон включительно.

leedm777
источник
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
ERRNO
источник
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 ))
Jesin
источник
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 }'
ghostdog74
источник
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

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

Cascabel
источник
Я использовал эту технику и заметил, что время от времени не будет генерироваться число, просто пробел.
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
valadil
источник
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

Или на OS-X у меня работает следующее:

$ gsort --random-sort
Чедвик Боггс
источник
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}'

zangw
источник
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
Yokai
источник