$RANDOM -- внутренняя функция Bash (не константа), которая возвращает псевдослучайные целые числа в диапазоне 0 - 32767. Функция $RANDOM не должна использоваться для генераци ключей шифрования.
Пример 9-23. Генерация случайных чисел
#!/bin/bash # $RANDOM возвращает различные случайные числа при каждом обращении к ней. # Диапазон изменения: 0 - 32767 (16-битовое целое со знаком). MAXCOUNT=10 count=1 echo echo "$MAXCOUNT случайных чисел:" echo "-----------------" while [ "$count" -le $MAXCOUNT ] # Генерация 10 ($MAXCOUNT) случайных чисел. do number=$RANDOM echo $number let "count += 1" # Нарастить счетчик. done echo "-----------------" # Если вам нужны случайные числа не превышающие определенного числа, # воспользуйтесь оператором деления по модулю (остаток от деления). RANGE=500 echo number=$RANDOM let "number %= $RANGE" echo "Случайное число меньше $RANGE --- $number" echo # Если вы желаете ограничить диапазон "снизу", # то просто производите генерацию псевдослучайных чисел в цикле до тех пор, # пока не получите число большее нижней границы. FLOOR=200 number=0 # инициализация while [ "$number" -le $FLOOR ] do number=$RANDOM done echo "Случайное число, большее $FLOOR --- $number" echo # Эти два способа могут быть скомбинированы. number=0 #initialize while [ "$number" -le $FLOOR ] do number=$RANDOM let "number %= $RANGE" # Ограничение "сверху" числом $RANGE. done echo "Случайное число в диапазоне от $FLOOR до $RANGE --- $number" echo # Генерация случайных "true" и "false" значений. BINARY=2 number=$RANDOM T=1 let "number %= $BINARY" # let "number >>= 14" дает более равномерное распределение # (сдвиг вправо смещает старший бит на нулевую позицию, остальные биты обнуляются). if [ "$number" -eq $T ] then echo "TRUE" else echo "FALSE" fi echo # Можно имитировать бросание 2-х игровых кубиков. SPOTS=7 # остаток от деления на 7 дает диапазон 0 - 6. ZERO=0 die1=0 die2=0 # Кубики "выбрасываются" раздельно. while [ "$die1" -eq $ZERO ] # Пока на "кубике" ноль. do let "die1 = $RANDOM % $SPOTS" # Имитировать бросок первого кубика. done while [ "$die2" -eq $ZERO ] do let "die2 = $RANDOM % $SPOTS" # Имитировать бросок второго кубика. done let "throw = $die1 + $die2" echo "Результат броска кубиков = $throw" echo exit 0
Пример 9-24. Выбор случайной карты из колоды
#!/bin/bash # pick-card.sh # Пример выбора случайного элемента массива. # Выбор случайной карты из колоды. Suites="Треф Бубей Червей Пик" Denominations="2 3 4 5 6 7 8 9 10 Валет Дама Король Туз" suite=($Suites) # Инициализация массивов. denomination=($Denominations) num_suites=${#suite[*]} # Количество элементов массивов. num_denominations=${#denomination[*]} echo -n "${denomination[$((RANDOM%num_denominations))]} " echo ${suite[$((RANDOM%num_suites))]} # $bozo sh pick-cards.sh # Валет Треф # Спасибо "jipe," за пояснения по работе с $RANDOM. exit 0
Jipe подсказал еще один способ генерации случайных чисел из заданного диапазона.
# Генерация случайных чисел в диапазоне 6 - 30. rnumber=$((RANDOM%25+6)) # Генерируется случайное число из диапазона 6 - 30, #+ но при этом число должно делиться на 3 без остатка. rnumber=$(((RANDOM%30/3+1)*3)) # Примечательно, если $RANDOM возвращает 0 # то это приводит к возникновению ошибки. # Упражнение: Попробуйте разобраться с выражением самостоятельно.
Bill Gradwohl предложил усовершенствованную формулу генерации положительных псевдослучайных чисел в заданном диапазоне.
rnumber=$(((RANDOM%(max-min+divisibleBy))/divisibleBy*divisibleBy+min))
В сценарии ниже, Bill представил универсальную функцию, которая возвращает псевдослучайное число из заданного диапазона.
Пример 9-25. Псевдослучайное число из заданного диапазона
#!/bin/bash # random-between.sh # Псевдослучайное число из заданного диапазона. # Автор: Bill Gradwohl, # незначительные изменения внесены автором документа. # Используется с разрешения автора сценария. randomBetween() { # Генерация положительных и отрицательных псевдослучайных чисел, #+ в диапазоне от $min до $max, #+ которые кратны числу $divisibleBy. # # Bill Gradwohl - Oct 1, 2003 syntax() { # Вложенная функция. echo echo "Порядок вызова: randomBetween [min] [max] [multiple]" echo echo "Функция ожидает до 3-х входных аргументов, но они не являются обязательными." echo "min -- нижняя граница диапазона" echo "max -- верхняя граница диапазона" echo "multiple -- делитель, на который должен делиться результат без остатка." echo echo "Если какой либо из параметров отсутствует, по-умолчанию принимаются значения: 0 32767 1" echo "В случае успеха функция возвращает 0, иначе -- 1" echo "и это сообщение о порядке вызова." echo "Результат возвращается в глобальной переменной randomBetweenAnswer" echo "Входные параметры, имеющие отрицательные значения, обрабатываются корректно." } local min=${1:-0} local max=${2:-32767} local divisibleBy=${3:-1} # При отсутствии какого либо из входных параметров, они принимают значения по-умолчанию. local x local spread # Делитель должен быть положительным числом. [ ${divisibleBy} -lt 0 ] && divisibleBy=$((0-divisibleBy)) # Проверка корректности входных параметров. if [ $# -gt 3 -o ${divisibleBy} -eq 0 -o ${min} -eq ${max} ]; then syntax return 1 fi # Если min больше чем max, то поменять их местами. if [ ${min} -gt ${max} ]; then # Поменять местами. x=${min} min=${max} max=${x} fi # Если min не делится без остатка на $divisibleBy, #+ то привести его к ближайшему подходящему числу из заданного диапазона. if [ $((min/divisibleBy*divisibleBy)) -ne ${min} ]; then if [ ${min} -lt 0 ]; then min=$((min/divisibleBy*divisibleBy)) else min=$((((min/divisibleBy)+1)*divisibleBy)) fi fi # Если max не делится без остатка на $divisibleBy, #+ то привести его к ближайшему подходящему числу из заданного диапазона. if [ $((max/divisibleBy*divisibleBy)) -ne ${max} ]; then if [ ${max} -lt 0 ]; then max=$((((max/divisibleBy)-1)*divisibleBy)) else max=$((max/divisibleBy*divisibleBy)) fi fi # --------------------------------------------------------------------- # А теперь собственно нахождение псевдослучайного числа. # Обратите внимание: чтобы получить псевдослучайное число в конце диапазона #+ необходимо рассматривать диапазон от 0 до #+ abs(max-min)+divisibleBy, а не abs(max-min)+1. # Этим превышением верхней границы диапазона можно пренебречь #+ поскольку эта новая граница никогда не будет достигнута. # Если использовать при вычислении формулу abs(max-min)+1, #+ то будут получаться вполне корректные значения, но при этом, #+ возвращаемые значения будут значительно ниже #+ верхней границы диапазона. # --------------------------------------------------------------------- spread=$((max-min)) [ ${spread} -lt 0 ] && spread=$((0-spread)) let spread+=divisibleBy randomBetweenAnswer=$(((RANDOM%spread)/divisibleBy*divisibleBy+min)) return 0 } # Проверка функции. min=-14 max=20 divisibleBy=3 # Создадим массив, который будет содержать счетчики встречаемости каждого из чисел #+ в заданном диапазоне. declare -a answer minimum=${min} maximum=${max} if [ $((minimum/divisibleBy*divisibleBy)) -ne ${minimum} ]; then if [ ${minimum} -lt 0 ]; then minimum=$((minimum/divisibleBy*divisibleBy)) else minimum=$((((minimum/divisibleBy)+1)*divisibleBy)) fi fi # Если max не делится без остатка на $divisibleBy, #+ то привести его к ближайшему подходящему числу из заданного диапазона. if [ $((maximum/divisibleBy*divisibleBy)) -ne ${maximum} ]; then if [ ${maximum} -lt 0 ]; then maximum=$((((maximum/divisibleBy)-1)*divisibleBy)) else maximum=$((maximum/divisibleBy*divisibleBy)) fi fi # Необходимое условие при работе с массивами -- #+ индекс массива должен быть положительным числом, #+ поэтому введена дополнительная переменная displacement, которая #+ гарантирует положительность индексов. displacement=$((0-minimum)) for ((i=${minimum}; i=${maximum}; i+=divisibleBy)); do answer[i+displacement]=0 done # Цикл с большим количеством итераций, чтобы посмотреть -- что мы получаем. loopIt=1000 # Автор сценария предложил 100000 итераций, #+ но в этом случае цикл работает чересчур долго. for ((i=0; i${loopIt}; ++i)); do # Обратите внимание: числа min и max передаются функции в обратном порядке, #+ чтобы продемонстрировать, что функция обрабатывает их корректно. randomBetween ${max} ${min} ${divisibleBy} # Вывод сообщения об ошибке, если функция вернула некорректное значение. [ ${randomBetweenAnswer} -lt ${min} -o ${randomBetweenAnswer} -gt ${max} ] && echo Выход за границы диапазона MIN .. MAX - ${randomBetweenAnswer}! [ $((randomBetweenAnswer%${divisibleBy})) -ne 0 ] && echo Число не делится на заданный делитель без остатка - ${randomBetweenAnswer}! # Записать полученное число в массив. answer[randomBetweenAnswer+displacement]=$((answer[randomBetweenAnswer+displacement]+1)) done # Проверим полученные результаты for ((i=${minimum}; i=${maximum}; i+=divisibleBy)); do [ ${answer[i+displacement]} -eq 0 ] && echo "Число $i не было получено ни разу." || echo "Число ${i} встречено ${answer[i+displacement]} раз." done exit 0
Насколько случайны числа, возвращаемые функцией $RANDOM? Лучший способ оценить "случайность" генерируемых чисел -- это написать сценарий, который будет имитировать бросание игрального кубика достаточно большое число раз, а затем выведет количество выпадений каждой из граней...
Пример 9-26. Имитация бросания кубика с помощью RANDOM
#!/bin/bash # Случайные ли числа возвращает RANDOM? RANDOM=$$ # Инициализация генератора случайных чисел числом PID процесса-сценария. PIPS=6 # Кубик имеет 6 граней. MAXTHROWS=600 # Можете увеличить, если не знаете куда девать свое время. throw=0 # Счетчик бросков. zeroes=0 # Обнулить счетчики выпадения отдельных граней. ones=0 # т.к. неинициализированные переменные - "пустые", и не равны нулю!. twos=0 threes=0 fours=0 fives=0 sixes=0 print_result () { echo echo "единиц = $ones" echo "двоек = $twos" echo "троек = $threes" echo "четверок = $fours" echo "пятерок = $fives" echo "шестерок = $sixes" echo } update_count() { case "$1" in 0) let "ones += 1";; # 0 соответствует грани "1". 1) let "twos += 1";; # 1 соответствует грани "2", и так далее 2) let "threes += 1";; 3) let "fours += 1";; 4) let "fives += 1";; 5) let "sixes += 1";; esac } echo while [ "$throw" -lt "$MAXTHROWS" ] do let "die1 = RANDOM % $PIPS" update_count $die1 let "throw += 1" done print_result # Количество выпадений каждой из граней должно быть примерно одинаковым, если считать RANDOM достаточно случайным. # Для $MAXTHROWS = 600, каждая грань должна выпасть примерно 100 раз (плюс-минус 20). # # Имейте ввиду, что RANDOM - это генератор ПСЕВДОСЛУЧАЙНЫХ чисел, # Упражнение: # --------------- # Перепишите этот сценарий так, чтобы он имитировал 1000 бросков монеты. # На каждом броске возможен один из двух вариантов выпадения - "ОРЕЛ" или "РЕШКА". exit 0
Как видно из последнего примера, неплохо было бы производить переустановку начального числа генератора случайных чисел RANDOM перед тем, как начать работу с ним. Если используется одно и то же начальное число, то генератор RANDOM будет выдавать одну и ту же последовательность чисел. (Это совпадает с поведением функции random() в языке C.)
Пример 9-27. Переустановка RANDOM
#!/bin/bash # seeding-random.sh: Переустановка переменной RANDOM. MAXCOUNT=25 # Длина генерируемой последовательности чисел. random_numbers () { count=0 while [ "$count" -lt "$MAXCOUNT" ] do number=$RANDOM echo -n "$number " let "count += 1" done } echo; echo RANDOM=1 # Переустановка начального числа генератора случайных чисел RANDOM. random_numbers echo; echo RANDOM=1 # То же самое начальное число... random_numbers # ...в результате получается та же последовательность чисел. # # В каких случаях может оказаться полезной генерация совпадающих серий? echo; echo RANDOM=2 # Еще одна попытка, но с другим начальным числом... random_numbers # получим другую последовательность. echo; echo # RANDOM=$$ в качестве начального числа выбирается PID процесса-сценария. # Вполне допустимо взять в качестве начального числа результат работы команд 'time' или 'date'. # Немного воображения... SEED=$(head -1 /dev/urandom | od -N 1 | awk '{ print $2 }') # Псевдослучайное число забирается #+ из системного генератора псевдослучайных чисел /dev/urandom , #+ затем конвертируется в восьмеричное число командой "od", #+ и наконец "awk" возвращает единственное число для переменной SEED. RANDOM=$SEED random_numbers echo; echo exit 0
Системный генератор /dev/urandom дает последовательность псевдослучайных чисел с более равномерным распределением, чем $RANDOM. Команда dd if=/dev/urandom of=targetfile bs=1 count=XX создает файл, содержащий последовательность псевдослучайных чисел. Однако, эти числа требуют дополнительной обработки, например с помощью команды od (этот прием используется в примере выше) или dd (см. Пример 12-45).
Есть и другие способы генерации псевдослучайных последовательностей в сценариях. Awk имеет для этого достаточно удобные средства.
Пример 9-28. Получение псевдослучайных чисел с помощью awk
#!/bin/bash # random2.sh: Генерация псевдослучайных чисел в диапазоне 0 - 1. # Используется функция rand() из awk. AWKSCRIPT=' { srand(); print rand() } ' # Команды/параметры, передаваемые awk # Обратите внимание, функция srand() переустанавливает начальное число генератора случайных чисел. echo -n "Случайное число в диапазоне от 0 до 1 = " echo | awk "$AWKSCRIPT" exit 0 # Упражнения: # --------- # 1) С помощью оператора цикла выведите 10 различных случайных чисел. # (Подсказка: вам потребуется вызвать функцию "srand()" # в каждом цикле с разными начальными числами. # Что произойдет, если этого не сделать?) # 2) Заставьте сценарий генерировать случайные числа в диапазоне 10 - 100 # используя целочисленный множитель, как коэффициент масштабирования # 3) То же самое, что и во втором упражнении, # но на этот раз случайные числа должны быть целыми.
Кроме того, команда date так же может использоваться для генерации псевдослучайных целых чисел.