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

2022-01-28Bash

はじめに

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

ちなみに 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 コマンドを使った後、 便利なオプションについて触れてみたいと思います。

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

標準入力 から情報を読み込ませる場合、パイプを使って 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つの数値をランダムに並べています。

$ shuf -i 2-4
2
3
4

$ shuf -i 2-4
4
3
2

数値しか利用できない点に注意です。

-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 オプション

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

想定される使い方としては sudo コマンドで実行し、実行結果を root 所有ファイルに書き出す、ぐらいでしょうか。

-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

任意の行データのみを採用してシャッフルしたい

標準入力に渡された情報のうち、任意の行データのみを採用してシャッフルしたい場合には以下のように sed と組み合わせて実現しましょう。

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

ひとこと

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

2022-01-28Bash