シェルでshufコマンドを使って入力情報をいろんな方法でシャッフルしてみる(組み合わせにも使える)

Bash

はじめに

実は 過去のブログエントリ で一度同じような内容を取り上げていたのですが、 shuf コマンドとオプションを組み合わせることでいろいろなシャッフルを実現できるので紹介します。

検証環境

$ uname -moi
x86_64 x86_64 GNU/Linux

$ bash -version | head -n 1
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)

shufコマンドとは

標準入力 あるいは コマンドライン引数として指定したファイル の各行を 入れ替え(シャッフル) して、出力するコマンドです。

両方の使い方でシャッフルさせてみた後、オプションについて紹介していきます。

標準入力から読み込ませる

標準入力 から情報を読み込ませる場合、パイプを使って shuf コマンドに引き渡すのがほとんどです。

1から5までの数値を shuf コマンドに食わせて、各行を入れかせてみます。

$ # seqコマンドを使うことで指定した数値を列挙することができます
$ seq 1 5
1
2
3
4
5

$ # seqコマンドの出力内容をshufコマンドに渡します。
$ seq 1 5 | shuf
5
1
3
4
2

$ # 2回目の実行。1回目と異なる出力となっていることがわかります。
$ seq 1 5 | shuf
3
1
2
5
4

ファイルを指定して読み込ませる

すでにシャッフルしたいデータファイルが存在する場合には、そちらを利用することも可能です。

$ # 適当なデータファイルを作成
$ seq 1 5 > nums.txt

$ # ファイルの中身を確認
$ cat nums.txt
1
2
3
4
5

$ # ファイルを指定してshufコマンドを実行します。
$ shuf nums.txt
3
5
1
2
4

$ # 2回目の実行。1回目と異なる出力となっていることがわかります。
$ shuf nums.txt
4
2
3
5
1

オプション

ランダムに出力させたい行を読み込ます方法について触れましたが、ここからは オプション について見ていきます。

$ shuf --help
Usage: shuf [OPTION]... [FILE]
  or:  shuf -e [OPTION]... [ARG]...
  or:  shuf -i LO-HI [OPTION]...
Write a random permutation of the input lines to standard output.

Mandatory arguments to long options are mandatory for short options too.
  -e, --echo                treat each ARG as an input line
  -i, --input-range=LO-HI   treat each number LO through HI as an input line
  -n, --head-count=COUNT    output at most COUNT lines
  -o, --output=FILE         write result to FILE instead of standard output
      --random-source=FILE  get random bytes from FILE
  -r, --repeat              output lines can be repeated

-e オプション

標準入力 あるいは ファイルを指定 することでランダムに出力させたいデータを読み込ませる以外にも、コマンドライン引数からランダム出力させたい文字列を渡すことが可能です。

$ shuf -e red blue yello green
yello
green
red
blue

# 2回目は出力内容が変わっています
$ shuf -e red blue yello green
green
yello
red
blue

ちょっとした内容であればこれでもいいかもしれません。(4人でゲームする際の開始順序を決めるとか。バドミントンのペアを決めるとか。)

-i オプション

読み込んだデータの内、何行目から何行目を取り出し、そのデータだけをランダムに並べることができます。

次の例では、2行目から4行目 の3行だけを取り出し、ランダムに並べています。1行目と5行目は除外されます。

$ seq 1 5 | shuf -i 2-4
2
3
4

$ seq 1 5 | shuf -i 2-4
4
3
2

便利ではありますが、他のコマンドと組み合わせて実現はできます。

以下の例では、 sed コマンドを使って2行目から4行目だけを取り出し、 shuf コマンドに渡しています。

$ seq 1 5 | sed -n '2,4p' | shuf
2
3
4

$ seq 1 5 | sed -n '2,4p' | shuf
4
2
3

-n オプション

シャッフルした行の内、指定した行数だけを出力させたい場合に利用します。
以下の例では、1行目から9行目の内容をランダムに並べ替えした後、3行分だけ出力します。

$ seq 1 9 | shuf -n 3
2
6
3

$ seq 1 9 | shuf -n 3
4
3
7

こちらも、他のコマンドと組み合わせて実現はできます。

例えば、 head コマンドを使って3行だけ取り出してやれば、同じことが実現可能です。

$ seq 1 9 | shuf | head -n 3
9
5
7

$ seq 1 9 | shuf | head -n 3
2
9
7

-o オプション

ランダムに行を並べ替えた結果をファイルに出力するオプションですが、 > リダイレクトを使えばよいのであまり出番がないかと。

-r オプション

場合によってはこのオプションを知っているか知らないかで便利さが変わってくると思っています。

今までのシャッフル方法は、出力結果に同じ行が登場することが有りませんでした。
例えるなら 「箱の中からくじを順番に引いていく。ただし引いたくじは箱に戻さない」 というケースでした。
-r オプションをつけるとこの挙動が 「箱の中からくじを順番に引いていく。引いたくじは毎回箱に戻す」 というものにかわります。

注意しないといけないこととして、 -nhead コマンドとの組み合わせはほぼ必須だということです。これをしないと延々とコマンドの実行が終了しません。

# 1から5まで出力された中から4つをランダムに選択する。重複の可能性があります。
$ seq 1 5 | shuf -r -n 4
1
2
4
4

# 1から5まで出力された中から4つをランダムに選択する。重複の可能性があります。
$ seq 1 5 | shuf -r -n 4
4
1
1
4

# *****これは -r オプションなしのケースです*****
$ seq 1 5 | shuf -n 4
4
1
2
3

# *****これは -r オプションなしのケースです*****
$ seq 1 5 | shuf -n 4
5
2
1
4

ひとこと

-r オプションをうまく使えば、組み合わせの問題を得にも使えそうです。

Bash