Bashシェルスクリプトでランダムな数値(乱数)を生成する

2023-03-27Bash,CentOS,Linux,Ubuntu,Zsh

はじめに

意外にも、初歩的な「乱数」に関するエントリを書いたことがなかった( _過去にサラリと触れていぐらいた_ )ようなので、今回ネタにすることにしました。

「ランダム」というキーワードを取り上げたエントリも参考に紹介します。

検証環境

$ uname -moi
x86_64 x86_64 GNU/Linux

$ head -n 2 /etc/os-release
NAME="Ubuntu"
VERSION="21.04 (Hirsute Hippo)"

$ bash -version | head -n 1
GNU bash, バージョン 5.1.4(1)-release (x86_64-pc-linux-gnu)

乱数を取得するための組み込み変数「RANDOM」を使う

Bash には、乱数を取得するための組み込み変数として RANDOM というものが用意されています。

試しに $RANDOM を出力してみましょう。

$ echo $RANDOM
458

$ echo $RANDOM
11854

echo $RANDOM を実行するたびに、出力される数値が異なります。

"RANDOM" 変数の最大値は?最小値は?

RANDOM 変数は参照するたびにランダムな値を返しますが、値の範囲はどうなっているでしょうか?

試しに $RANDOM を 1,000,000 回 出力した結果を集計してみます。

# 1,000,000回 ループして、 RANDOM 変数を参照する。
# 結果を ソート → 重複除去 してファイルに書き出す。
$ for i in {0..1000000}; do echo ${RANDOM}; done | sort -n | uniq > result.txt

# ファイルの行数は 32,768 行
$ wc -l result.txt
32768 result.txt

# 先頭5行を確認
$ head -n 5 result.txt
0
1
2
3
4

# 末尾5行を確認
$ tail -n 5 result.txt
32763
32764
32765
32766
32767

このように、 "RANDOM" 変数は 0 〜 32767 の間でランダムな数値を返却します

( 2 の 15 乗分の数値がランダムに算出されます。 )

# 2の15乗 を算出
$ echo $((2**15))
32768

任意の範囲の乱数を求める方法( ex: 0〜5 )

任意の範囲の乱数を求めたい場合にはどうしたらよいでしょう?

例えば、 サイコロの出目として 6 つの数値( 0〜5 )をランダムに取得したい場合 はどうしたら良いでしょう?

この場合も RANDOM 変数を利用することができます。

# $RAMDOM % 出目の数 を算出する
$ echo $((RANDOM % 6))
1

$ echo $((RANDOM % 6))
3

10,000 回 出力した結果を集計してみます。

# 10,000回 0〜5 までの数値を出力し、、数を数える
$ for i in {0..10000}; do echo $((RANDOM % 6)); done | sort -n | uniq -c > result2.txt

# 0〜5 の数値がそれぞれ、おおよそ 1667回 出力されている
$ cat result2.txt
   1690 0
   1669 1
   1701 2
   1620 3
   1669 4
   1652 5

コインの出目を求めたい場合には 2 で割ったあまり を算出します。

$ echo $((RANDOM % 2))
0

$ echo $((RANDOM % 2))
0

$ echo $((RANDOM % 2))
1

【2022-10-16 追記】6 つの数値(0〜5)をランダムに取得する際の注意

Twitter にてアドバイスを頂きました!

この方法だと、ごく僅かではありますが偏りが発生します ( 0 と 1 だけ、他の出目より 1/32767 だけ多くでる )。

これは RANDOM 変数が 0 〜 32767 ( 20-1 〜 215-1 )の間でランダムな数値を返却するためです。

RANDOM 変数の値を 3 つ加算 ( 0 〜 98303 ) し、範囲内に登場する数値の数が 6 で割り切れるようにすることでこの問題を解消できます。

$ echo $(((RANDOM + RANDOM + RANDOM) % 6))
0

$ echo $(((RANDOM + RANDOM + RANDOM) % 6))
2

"32767" 以上の乱数を算出したい場合は?

"32767" 以上の乱数を求めたい場合にはどうしたら良いでしょう?

RANDOM 変数を複数回加算する

Twitter にてアドバイスを頂きました!

RANDOM 変数を複数回足し合わせることで 32767 の上限を引き上げることができます。

RANDOM + RANDOM で 0 〜 65535 、 RANDOM + RANDOM + RANDOM で 0 〜 98303 のランダム値を求めることができます。

# 0 〜 65535 のランダム値を算出
$ echo $((RANDOM + RANDOM))
26306

$ echo $((RANDOM + RANDOM))
35702

# 0 〜 98303 のランダム値を算出
$ echo $((RANDOM + RANDOM + RANDOM))
21338

$ echo $((RANDOM + RANDOM + RANDOM))
70054

shuf コマンドを使う

shuf コマンドを使って 「1 から 100,000」 までの乱数を算出してみます。

$ shuf -i 1-100000 -n 1
92326

$ shuf -i 1-100000 -n 1
8180

$ shuf -i 1-100000 -n 1
28623

shuf コマンドがない場合は?

shuf コマンドがない場合は、以下のように seqsorthead を組み合わせて頑張りましょう。

# sort -R で標準入力をランダムに入れ替える
$ seq 1 100000 | sort -R | head -n 1
89938

$ seq 1 100000 | sort -R | head -n 1
11193

$ seq 1 100000 | sort -R | head -n 1
4539

ただ、複数コマンドを組み合わせていることもあり、 遅い です。

ひとこと

使えるなら RANDOM で実現するのがよいでしょう。

2023-03-27Bash,CentOS,Linux,Ubuntu,Zsh