9.6. $RANDOM: 랜덤한 정수 만들기

참고: $RANDOM 은 bash 내부 함수(상수가 아님)로 0에서 32767사이의 의사난수(pseudorandom)를 리턴합니다. $RANDOM 은 암호화 키를 발생 시키는데 쓸 수 없습니다.

예 9-19. 랜덤한 숫자 만들기

#!/bin/bash

# $RANDOM 은 불릴 때마다 다른 무작위 정수 값을 리턴합니다.
# 명칭상의 범위(nominal range): 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 "-----------------"

# 어떤 범위의 랜덤 값이 필요하다면 '나머지(modulo)' 연산자를 쓰면,
# 어떤 수를 나눈 나머지 값을 리턴해 줍니다.

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   # 초기화
while [ "$number" -le $FLOOR ]
do
  number=$RANDOM
  let "number %= $RANGE"  # $number 가 $RANGE 안에 들어오게.
done
echo "$FLOOR 와 $RANGE 사이의 랜덤한 숫자  ---  $number"
echo


# "참"이나 "거짓"중에 하나를 고르도록 할 수도 있습니다.
BINARY=2
number=$RANDOM
T=1

let "number %= $BINARY"
# let "number >>= 14"    더 좋은 분포의 랜덤값을 줍니다.
# (마지막 두 비트를 제외하고 모두 오른쪽으로 쉬프트 시킴).
if [ "$number" -eq $T ]
then
  echo "TRUE"
else
  echo "FALSE"
fi  

echo


# 주사위 던지기를 흉내내 볼까요?
SPOTS=7   # 7 의 나머지(modulo)는 0 - 6.
DICE=2
ZERO=0
die1=0
die2=0

# 정확한 확률을 위해서 두 개의 주사위를 따로 던집시다.

  while [ "$die1" -eq $ZERO ]     # 주사위에 0은 없죠.
  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

RANDOM 이 얼마나 랜덤할까요? 이걸 확인해 보는 가장 좋은 방법은 RANDOM 이 발생 시키는 "랜덤"한 숫자의 분포를 추적하는 스크립트를 만들어 보는 것입니다. 그럼 RANDOM 주사위를 몇 번 던져 봅시다.

예 9-20. RANDOM 으로 주사위를 던지기

#!/bin/bash
# RANDOM 이 얼마나 랜덤한가?

RANDOM=$$       # 스크립트의 프로세스 ID 를 써서 랜덤 넘버 발생기의 seed를 다시 생성.

PIPS=6          # 주사위는 눈이 6개죠.
MAXTHROWS=600   # 시간이 남아 돌면 이 숫자를 더 늘려보세요.
throw=0         # 던진 숫자.

zeroes=0        # 초기화를 안 하면 0이 아니라 널이 되기 때문에
ones=0          # 0으로 초기화 해야 됩니다.
twos=0
threes=0
fours=0
fives=0
sixes=0

print_result ()
{
echo
echo "ones =   $ones"
echo "twos =   $twos"
echo "threes = $threes"
echo "fours =  $fours"
echo "fives =  $fives"
echo "sixes =  $sixes"
echo
}

update_count()
{
case "$1" in
  0) let "ones += 1";;   # 주사위에 "0"이 없으니까 이걸 1이라고 하고
  1) let "twos += 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 이 의사난수(pseudorandom) 발생기이기 때문에
# 진짜로 랜덤한 것은 아니라는 점입니다.

# 쉬운 연습문제 하나 낼께요.
# 이 스크립트를 동전 1000 번 뒤집는 걸로 바꿔보세요.
# "앞"이나 "뒤" 중에 하나가 나오겠죠?

exit 0

위의 예제에서 살펴본 것처럼 RANDOM 발생기가 실행될 때마다 랜덤용 seed를 "다시" 만들어 주는 것이 좋습니다. RANDOM에 같은 seed를 쓰면 같은 순서의 숫자가 반복됩니다(이는 C의 random() 함수의 동작을 그대로 반영해 줍니다).

예 9-21. RANDOM 에 seed를 다시 지정해 주기

#!/bin/bash
# seeding-random.sh: RANDOM 변수에 seed 적용.

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 seed 세팅.
random_numbers

echo; echo

RANDOM=1          # 똑같은 seed 사용....
random_numbers    # ... 완전히 똑같은 수열이 발생.

echo; echo

RANDOM=2          # 다른 seed 로 재시도...
random_numbers    # 다른 수열 발생.

echo; echo

# RANDOM=$$  라고 하는 것은 RANDOM 에 스크립트의 프로세스 ID로 seed를 세팅하는 것입니다.
# 'time' 이나 'date'로 할 수도 있겠네요.

# 더 멋지게 해 보죠.
SEED=$(head -1 /dev/urandom | od -N 1 | awk '{ print $2 }')
# /dev/urandom(시스템에서 제공하는 의사난수 "디바이스")에서 얻은 의사난수
# 그 다음 "od"를 이용해서 출력 가능한(8진수) 숫자로 이루어진 줄로 변환.
# 마지막으로 "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-33 참고)로 필터링을 하는 등의 처리가 필요합니다.