複数行ファイルのシャッフル

複数行ファイルのシャッフル

テキスト ブロックを区切る空行のあるテキスト ファイルがあります。*NIX コマンドライン ツールを使用して、ブロック構造を尊重しながらこのファイルをシャッフルしたいと思います。つまり、出力でブロックの順序が変更されたことを確認したいのですが、ブロック内の行とその順序は同じままです。

入力ファイルの例:

line 1
line 2

line 10
line 20
line 30

line 100
line 200

出力ファイル(シャッフル後):

line 10
line 20
line 30

line 1
line 2

line 100
line 200

もちろん、繰り返し実行するとブロックの順序は異なります。

ファイルの最初の行は常に空ではありません。二重の空白行はありません。ファイルの最後の行は常に空です。

リストのリスト内のすべての行を読み取り、シャッフルして出力する非常に単純な Python スクリプトを作成しました。標準の *NIX ツールでこれが実行できるかどうか興味があります。

答え1

POSIX では、次のようにすることができます:

<file awk '
  BEGIN{srand(); n=rand()}
  {print n, NR, $0}
  !NF {n=rand()}
  END {if (NF) print n, NR+1, ""}' |
  sort -nk1 -k2 |
  cut -d' ' -f3-

つまり、各行の先頭に<a-random-number-that-changes-with-each-paragraph>行番号を付け、最初の番号で数値的に並べ替え、次に 2 番目の番号で並べ替えて段落内の行の順序を維持し、余分な番号を削除します。

sed '$d'末尾の空白行を削除するには、パイプを使用する必要があります。

ほとんどのawk実装 ではsrand()、疑似乱数ジェネレータのシードにUNIXエポックタイムを使用しているため、同じ秒に2回実行すると同じ結果が得られる可能性があることに注意してください(残念ながら私の努力にもかかわらず、歴史的なバグがPOSIX仕様に刻まれてしまった)。

答え2

GNU ツールを使用して、段落を NUL で区切られたグループに分割し、シャッフルしてから NUL を削除します。

$ sed '1s/^/\n/; s/^$/\x00/' input | shuf -z | sed '1d; s/\x00//'
line 100
line 200

line 10
line 20
line 30

line 1
line 2

NULを使用しない代替アプローチ

すべてのツールが NUL 文字をサポートしているわけではないので、代替手段を紹介します。これは段落を読み取り、~改行に置き換え、シャッフルし、~改行に戻してから結果を表示します。

$ awk '{gsub(/\n/, "~")} 1' RS= input | shuf | awk '{gsub(/~/, "\n")} 1' ORS="\n\n"
line 10
line 20
line 30

line 100
line 200

line 1
line 2

テキストに が含まれる可能性がある場合は~、一時的な行区切り文字として、テキストに含まれない別の文字を使用します。

答え3

perl を使用する場合:

perl -MList::Util -00 -e 'chomp(my @a=<>); print join("\n\n", List::Util::shuffle @a) . "\n";' < input

または、スクリプト ファイルとして展開します。

#!/usr/bin/perl
use List::Util 'shuffle';
local $/ = "";  ## paragraph mode
chomp(my @a = <>);
print join("\n\n", shuffle @a) . "\n";

関連情報