Перейти к основному содержанию
Рецепты Linux

Main navigation

  • Основы
  • Система
  • Команды
  • Программы
  • Дистро
  • Интерфейсы
  • Устройства
  • Доки
User account menu
  • Войти

Строка навигации

  1. Главная
  2. ABS Guide
  3. Часть 3. Углубленный материал
  4. Глава 9. К вопросу о переменных

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

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 так же может использоваться для генерации псевдослучайных целых чисел.

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

  • 9.5. Косвенные ссылки на переменные
  • Вверх
  • 9.7. Двойные круглые скобки

Book navigation

  • Содержание
  • Часть 1. Введение
  • Часть 2. Основы
  • Часть 3. Углубленный материал
    • Глава 9. К вопросу о переменных
      • 9.1. Внутренние переменные
      • 9.2. Работа со строками
      • 9.3. Подстановка параметров
      • 9.4. Объявление переменных: declare и typeset
      • 9.5. Косвенные ссылки на переменные
      • 9.6. $RANDOM: генерация псевдослучайных целых чисел
      • 9.7. Двойные круглые скобки
    • Глава 10. Циклы и ветвления
    • Глава 11. Внутренние команды
    • Глава 12. Внешние команды, программы и утилиты
    • Глава 13. Команды системного администрирования
    • Глава 14. Подстановка команд
    • Глава 15. Арифметические подстановки
    • Глава 16. Перенаправление ввода/вывода
    • Глава 17. Встроенные документы
  • Часть 4. Материал повышенной сложности
  • Глава 35. Замечания и дополнения
  • Библиография
  • Приложение A. Дополнительные примеры сценариев
  • Приложение B. Справочная информация
  • Приложение C. Маленький учебник по Sed и Awk
  • Приложение D. Коды завершения, имеющие предопределенный смысл
  • Приложение E. Подробное введение в операции ввода-вывода и перенаправление ввода-вывода
  • Приложение F. Системные каталоги
  • Приложение G. Локализация
  • Приложение H. История команд
  • Приложение I. Пример файла .bashrc
  • Приложение J. Преобразование пакетных (*.bat) файлов DOS в сценарии командной оболочки
  • Приложение K. Упражнения
  • Приложение L. Хронология
  • Приложение M. Авторские права

Последние материалы

  • Приложение scanimage
    5 hours ago
  • Утилита sensors
    4 days ago
  • Сканер Rkhunter
    1 week 4 days ago
  • Программа resize2fs
    2 weeks 3 days ago
  • Аудиопроигрыватель QMMP
    3 weeks 1 day ago
RSS feed

Secondary menu

  • О проекте

© 2008–2025 Олег Меньшенин mensh@yandex.ru