9.6. $RANDOM: генерация псевдослучайных целых чисел

$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

      
Note

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

      

Note

Системный генератор /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) То же самое, что и во втором упражнении,
#      но на этот раз случайные числа должны быть целыми.