Advanced Bash-Scripting Guide: Искусство программирования на языке сценариев командной оболочки | ||
---|---|---|
Назад | Глава 9. К вопросу о переменных | Вперед |
$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? Лучший способ оценить "случайность" генерируемых чисел -- это написать сценарий, который будет имитировать бросание игрального кубика достаточно большое число раз, а затем выведет количество выпадений каждой из граней...
Пример 9-25. Имитация бросания кубика с помощью 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-26. Переустановка 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-42). Есть и другие способы генерации псевдослучайных последовательностей в сценариях. Awk имеет для этого достаточно удобные средства. Пример 9-27. Получение псевдослучайных чисел с помощью 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) То же самое, что и во втором упражнении, # но на этот раз случайные числа должны быть целыми. |