シェルで乱数($RANDOM, /dev/random)を扱う方法

シェルで乱数($RANDOM, /dev/random)を扱う方法

シェルスクリプト、コマンドでの乱数

シェルスクリプトやコマンドで乱数を扱う方法をまとめます。

幾つかの手段が用意されており、それぞれできることが微妙に異なります。

組み込み変数の $RANDOM

シェル(bashzsh)が提供する組み込みの変数 $RANDOM は、参照するたびに0から32767までのランダムな整数(16bit)が生成されます。

$ echo $RANDOM
27053

$RANDOM を5回連続で参照すると、次のように異なる値が生成されることが確認できます。

$ for i in { 1 .. 5 }; do echo $RANDOM; done
32066
1856
9836
10184
31369

実行結果は環境により異なりますが、参照のたびに異なる整数値が返されていることがわかります。

任意の整数区間から乱数を生成

剰余演算を組み合わせることで 0-32767 の区間ではなく、任意の連続する区間から乱数を取得できます。

例えば0から9の範囲で乱数を生成するには乱数を10で割った余りを使います。

# 0-9 の範囲で乱数生成
$ echo $(($RANDOM % 10))
4

1から10の範囲を使用したい場合は、剰余演算の結果に1を足してやればよいです。

# 1-10 の範囲で乱数生成
$ echo $(($RANDOM % 10 + 1))
1

最後に負数も含めたい場合も同様に結果をマイナスしてやればよいです。

$ echo $(($RANDOM % 3 - 1))
1
$ echo $(($RANDOM % 3 - 1))
0
$ echo $(($RANDOM % 3 - 1))
-1

上の例は $RANDOM % 3 で '0,1,2' のいずれかの整数が得られます。その結果から -1 してやれば最終的に '-1,0,1' からランダムに整数が得られます。

/dev/random を使って真の乱数を取得する

Linux カーネルは乱数を扱う疑似デバイスファイルを提供しています。これを使用すればシェルの種類によらず乱数を扱えます。様々な情報源から集めた環境ノイズを利用して真の乱数性を得ることができるように設計されています。

/dev/random は複数の環境ノイズをエントロピーとし、ランダムなバイト列をバッファします。したがってこの /dev/random を読み出せばランダムなバイト列を得ることができ、これを変換して乱数として扱うことが可能です。

例えば /dev/random から8バイト読み出してみます。

$ head -c 8 /dev/random
CzE

8バイトのバイト列ですが印字可能な文字とは限りません。文字化けしたみたいな内容になると思います。読み出すたびに結果が変わります。

od コマンドでバイト列を整数値に変換して乱数を得る

/dev/random コマンドから読みだしたバイト列を od コマンドで整数値にしてダンプすればランダムな整数値が得られます。

# 4byte(32bit)の符号なし整数値をランダムに取得
$ od -An -tu4 -N4 /dev/random
  491172340

od コマンドのオプションの意味は以下の通りです。

  • -An: 各行に表示されるアドレス表示を非表示にします
  • -tu4: 4byte(32bit)で10進数表示します
  • -N4: ファイルから4byte(32bit)読み出します

これで4byteの乱数が1つ得られます。読み出すデータサイズを変更したければ 4 となっている個所を適宜変更してください。4byte だと 32bit 符号なし整数が得られます。

バイトで乱数のサイズを指定するので

  • 1byte: 0-255
  • 2byte: 0-65,535
  • 3byte: 0-16,777,215
  • 4byte: 0-4,294,967,295

の範囲で整数の結果が得られます。

左に余計な空白があるので消したければ、トリムしましょう。方法はいくつかありますがおすきにどうぞ。

# tr -d でスペースを削除
$ od -An -tu4 -N4 /dev/random | tr -d ' '
1961272748

# $(()) で展開する
$ echo $((`od -An -tu4 -N4 /dev/random`))
3362491708

乱数の生成区間を変更したい場合は、$RANDOM の時と同じく剰余演算と加減算をすればよいです。

-128..127 までの区間で乱数を生成するコマンドは以下の通りです。

$ echo $((`od -An -tu1 -N1 /dev/random` - 128))
-73

/dev/random の注意点

/dev/random から読み出すバイト数は最小限にしておいたほうが良いです。読みだしたバイト列は消費されます。

/dev/random は環境ノイズを使用するのでエントロピーを消費し尽くせば、再び環境ノイズが得られるまで処理がブロックされます。

この処理がブロックされる可能性に注意しなければなりませんが、ブロックされないように実装された乱数生成のための疑似デバイスファイルがあります。それが /dev/urandom です。

/dev/urandom を使って疑似乱数を生成する

/dev/random は真の乱数性を得るために設計されていますが、/dev/urandom は疑似乱数を生成するために使われます。

urandomu は 'unblock' の頭文字に由来します。つまりエントロピープールが枯渇しても処理がブロックされることなく乱数を生成し続けることが可能です。ただし、その反面枯渇すると乱数シードが再利用されるので精度的には /dev/random に劣るようです。

使い方は /dev/random と同じです。参照する疑似デバイスファイルを /dev/urandom にするだけです。

$ od -An -tu1 -N1 /dev/urandom
 137

shuf で乱数を得る

shuf コマンド は -i オプションを使って任意の整数範囲から出力した整数列をランダムに並べ替えることができます。また、-n オプションでシャッフルした結果から表示する行数を指定できます。

このオプション -i-n を組み合わせて任意の範囲の整数からランダムに1件を選ぶことができます。つまり乱数が生成できます。

$ shuf -i 0-255 -n 1
131

乱数の生成という観点からは、shuf を使うことは余りおすすめできません。/dev/random(/dev/Urandom) もしくは $RANDOM を使ったほうがよいでしょうが、手段の1つとして紹介しておきます。

そのほかにも openssl randjot -r などの手段もあるみたいですが、ここでは紹介しません。

余りを使って乱数を得るときの注意

余りを使えば任意の範囲でランダムな整数を得られますが、余りをとる前の数としてありうる個数を割り切る数で余りを取らないと最終的な乱数に偏りが生じます。

例えば0以上255以下のランダムな整数を生成する od -An -tu1 -N1 /dev/urandom という処理を使って得られた乱数を、0から99の範囲の整数に変換するために余りをとると、0から55の範囲の数は56-99の範囲の数よりも出現回数が多くなります。

これは 256 が 100 で割り切れないために生じています。

対策として、割り切れる数で余りをとるか、余りを取る数よりも十分に大きな範囲で乱数を生成するのが良いみたいです。

/dev/random を使ってランダムな文字列を生成する

ランダムな整数ではなくランダムな文字列を生成する方法を考えます。

/dev/random もしくは /dev/urandom を読み出すとランダムなバイト列が得られます。これを base64 でエンコードするとアルファベット26文字の大文字小文字、0-9の半角数字、'-'、'/' の64種類の文字からなるランダムな文字列が得られます。ただし、'=' がエンコードの都合で末尾につけられるので無視します。

# 10byte ランダムなバイト列を読み出して base64 でエンコード
$ head -c 10 /dev/urandom | base64 | tr -d '='
2XURgcJONhHzdw

head -c 10 で10byteのバイト列を読み出して base64 コマンドでエンコードします。base64 のエンコードは末尾に '=' がデータによってつけられるので削除しています。

簡単にワンラインで書くならこれが簡単にかけるかと思います。

文字数や文字種を制御するならシェルスクリプトで関数として定義したりして書いたほうが良いと思います。

以上。

参考URL

Linuxカテゴリの最新記事