关于随机:如何在Unix命令行或shell脚本中随机播放文本文件的行?

How can I shuffle the lines of a text file on the Unix command line or in a shell script?

我想随机移动文本文件的行并创建一个新文件。文件可能有几千行。

我怎样才能用catawkcut等?


您可以使用shuf。至少在某些系统上(似乎不在POSIX中)。

正如jleedev所指出的:sort -R也可能是一种选择。至少在某些系统上是这样的;好吧,你能看到照片。有人指出,sort -R不是真正的无序排列,而是根据散列值对项目进行排序。

[编者按:sort -R几乎乱序排列,除了重复的行/排序键总是彼此相邻。换句话说:只有使用唯一的输入行/键,它才是真正的随机播放。虽然输出顺序是由散列值确定的,但随机性来自于选择随机散列函数-请参阅手册。]


PerlOne班轮将是Maxim解决方案的简单版本

1
perl -MList::Util=shuffle -e 'print shuffle(<STDIN>);' < myfile


这个答案通过以下方式补充了许多现有的答案:

  • 现有答案打包成灵活的shell函数:

    • 函数不仅接受stdin输入,还接受文件名参数。
    • 这些函数采取额外的步骤,以通常的方式处理SIGPIPE(安静地终止,退出代码为141),而不是大声中断。当将功能输出管道输送到早期关闭的管道时,这一点很重要,例如,当管道输送到head时。
  • 进行了性能比较。

  • 基于awksortcut的posix兼容函数,改编自op自己的答案:
1
2
shuf() { awk 'BEGIN {srand(); OFMT="%.17f"} {print rand(), $0}'"$@" |
               sort -k1,1n | cut -d ' ' -f2-; }
  • 基于Perl的函数-改编自Moonyoung Kang的答案:
1
shuf() { perl -MList::Util=shuffle -e 'print shuffle(<>);'"$@"; }

  • 基于python的函数,改编自scai的答案:
1
2
3
4
5
shuf() { python -c '
import sys, random, fileinput; from signal import signal, SIGPIPE, SIG_DFL;    
signal(SIGPIPE, SIG_DFL); lines=[line for line in fileinput.input()];  
random.shuffle(lines); sys.stdout.write("".join(lines))
'"$@"; }
  • 基于Ruby的函数,改编自Hoffmanc的答案:
1
2
shuf() { ruby -e 'Signal.trap("SIGPIPE","SYSTEM_DEFAULT");
                     puts ARGF.readlines.shuffle'"$@"; }

性能比较:

注:这些数字是在运行OSX 10.10.3的带有3.2 GHz Intel Core i5和Fusion Drive的2012年末iMac上获得的。虽然时间会随所用操作系统、机器规格、使用的awk实现而变化(例如,在osx上使用的bsd awk版本通常比gnu awk版本慢,尤其是mawk版本慢),但这应该提供相对性能的一般意义。

输入文件是用seq -f 'line %.0f' 1000000生成的一百万行文件。时间按升序排列(最快优先):

  • 埃多克斯1〔12〕
    • 埃多克斯1〔13〕
  • 红宝石2.0.0
    • 埃多克斯1〔14〕
  • Perl 5.18.2版
    • 埃多克斯1〔15〕
  • Python
    • 带python 2.7.6的1.342s2.407s!)使用python 3.4.2
  • awk+sort+cut
    • 3.003s与bsd awk2.388s与gnu EDOCX1(4.1.1);1.811smawk(1.3.4);

为了进一步比较,未打包为上述功能的解决方案:

  • sort -R(如果有重复的输入行,则不是真正的无序排列)
    • 10.661s—分配更多的内存似乎没有什么区别。
  • 斯卡拉
    • 江户十一〔29〕号
  • bash环+sort
    • 埃多克斯1〔32〕

结论:

  • 如果可以的话,使用shuf,这是目前为止最快的。
  • Ruby做得很好,后面是Perl。
  • Python明显比Ruby和Perl慢,而且,与Python版本相比,2.7.6比3.4.1快得多。
  • 使用符合POSIX的awksortcut组合作为最后手段;您使用的awk实现事项(mawk比gnu awk快,bsd awk慢)。
  • 远离sort -Rbash环和scala。

我使用一个小Perl脚本,我称之为"unsort":

1
2
3
4
#!/usr/bin/perl
use List::Util 'shuffle';
@list = <STDIN>;
print shuffle(@list);

我还有一个空分隔的版本,叫做"unsort0"…方便与find-print0等一起使用。

附言:我也投了"嘘"的票,我不知道这些天在科雷Utils那里……如果您的系统没有"shuf",上面的内容可能仍然有用。


这是第一次尝试,它在编码器上很容易,但在CPU上很难,它为每一行预先准备一个随机数,对它们进行排序,然后从每一行中去掉随机数。实际上,这些行是随机排序的:

1
cat myfile | awk 'BEGIN{srand();}{print rand()"\t"$0}' | sort -k1 -n | cut -f2- > myfile.shuffled


这是一个awk脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
awk 'BEGIN{srand() }
{ lines[++d]=$0 }
END{
    while (1){
    if (e==d) {break}
        RANDOM = int(1 + rand() * d)
        if ( RANDOM in lines  ){
            print lines[RANDOM]
            delete lines[RANDOM]
            ++e
        }
    }
}' file

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ cat file
1
2
3
4
5
6
7
8
9
10

$ ./shell.sh
7
5
10
9
6
8
2
1
3
4


针对python的一行程序:

1
python -c"import random, sys; lines = open(sys.argv[1]).readlines(); random.shuffle(lines); print ''.join(lines)," myFile

对于只打印一条随机行:

1
python -c"import random, sys; print random.choice(open(sys.argv[1]).readlines())," myFile

但请参阅本文了解Python的random.shuffle()的缺点。它不能与许多(超过2080)元素一起工作。


简单的基于awk的函数将完成以下工作:

1
2
3
4
shuffle() {
    awk 'BEGIN{srand();} {printf"%06d %s
", rand()*1000000, $0;}' | sort -n | cut -c8-
}

用途:

1
any_command | shuffle

这几乎可以在任何UNIX上工作。在Linux、Solaris和HP-UX上测试。

更新:

注意,前导零(%06drand()乘法使得它在sort不懂数字的系统上也能正常工作。它可以通过词典编纂顺序(也就是正常字符串比较)进行排序。


红宝石FTW:

1
ls | ruby -e 'puts STDIN.readlines.shuffle'


一个基于scai答案的python行程序,但是a)接受stdin,b)使用seed使结果可重复,c)只选择200行。

1
2
3
$ cat file | python -c"import random, sys;
  random.seed(100); print ''.join(random.sample(sys.stdin.readlines(), 200))," \
  > 200lines.txt


一种简单直观的方法是使用shuf

例子:

假设words.txt为:

1
2
3
4
5
6
7
the
an
linux
ubuntu
life
good
breeze

要洗牌,请执行以下操作:

1
$ shuf words.txt

这会将无序排列的行抛出到标准输出;因此,您必须将其传输到输出文件,如:

1
$ shuf words.txt > shuffled_words.txt

这样的洗牌可以产生:

1
2
3
4
5
6
7
breeze
the
linux
an
ubuntu
good
life


我们有一套方案来完成这项工作:

1
sudo apt-get install randomize-lines

例子:

创建编号的有序列表,并将其保存到1000.txt:

1
seq 1000 > 1000.txt

要洗牌,只需使用

1
rl 1000.txt


如果像我一样,你来这里是为了寻找替代MacOS的shuf,那么使用randomize-lines

安装randomize-lines包(自制),该包有一个rl命令,其功能与shuf相似。

埃多克斯1〔48〕

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Usage: rl [OPTION]... [FILE]...
Randomize the lines of a file (or stdin).

  -c, --count=N  select N lines from the file
  -r, --reselect lines may be selected multiple times
  -o, --output=FILE
                 send output to file
  -d, --delimiter=DELIM
                 specify line delimiter (one character)
  -0, --null     set line delimiter to null character
                 (useful with find -print0)
  -n, --line-number
                 print line number with output lines
  -q, --quiet, --silent
                 do not output any errors or warnings
  -h, --help     display this help and exit
  -V, --version  output version information and exit


这是我在主文件夹中保存为rand.py的python脚本:

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/python

import sys
import random

if __name__ == '__main__':
  with open(sys.argv[1], 'r') as f:
    flist = f.readlines()
    random.shuffle(flist)

    for line in flist:
      print line.strip()

在mac osx sort -Rshuf上不可用,因此可以在bash_配置文件中将其命名为:

1
alias shuf='python rand.py'


如果安装了scala,这里有一个单行程序来处理输入:

1
ls -1 | scala -e 'for (l <- util.Random.shuffle(io.Source.stdin.getLines.toList)) println(l)'


此bash函数具有最小的依赖关系(仅限sort和bash):

1
2
3
4
5
6
7
8
shuf() {
while read -r x;do
    echo $RANDOM$'\x1f'$x
done | sort |
while IFS=$'\x1f' read -r x y;do
    echo $y
done
}


至今未提及:

  • unsort实用程序。语法(有点面向播放列表):

    1
    2
    3
    4
    unsort [-hvrpncmMsz0l] [--help] [--version] [--random] [--heuristic]
           [--identity] [--filenames[=profile]] [--separator sep] [--concatenate]
           [--merge] [--merge-random] [--seed integer] [--zero-terminated] [--null]
           [--linefeed] [file ...]

  • msort可以按行移动,但通常是杀伤力过大:

    1
    seq 10 | msort -jq -b -l -n 1 -c r

  • 另一个awk变体:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #!/usr/bin/awk -f
    # usage:
    # awk -f randomize_lines.awk lines.txt
    # usage after"chmod +x randomize_lines.awk":
    # randomize_lines.awk lines.txt

    BEGIN {
      FS ="
    ";
      srand();
    }

    {
      lines[ rand()] = $0;
    }

    END {
      for( k in lines ){
        print lines[k];
      }
    }


    在Windows中,您可以尝试此批处理文件以帮助您洗牌data.txt,批处理代码的用法是

    1
    C:\> type list.txt | shuffle.bat > maclist_temp.txt

    发出此命令后,maclist_temp.txt将包含行的随机列表。

    希望这有帮助。