如何在Unix命令行或shell脚本中打乱文本文件的行?

我想随机随机洗牌的行,并创build一个新的文件。 该文件可能有几千行。

我怎么能用猫,awk,cut等来做到这一点?

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

jleedev指出: sort -R也可能是一个选项。 至less在一些系统上; 好吧,你得到的照片。 有人指出 sort -R并不真正洗牌,而是根据它们的散列值对项目进行sorting。

[编者按: sort -R 几乎洗牌,除了重复的行/sorting键总是彼此相邻 换句话说:只有唯一的input行/键才是真正的洗牌。 虽然输出顺序是由散列值确定的,但随机性来自于select随机散列函数 – 请参阅手册 。

Perl单线程将是Maxim解决scheme的简单版本

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

这个答案在以下几个方面补充了现有的很多答案:

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

    • 这些函数不仅要使用stdininput,还要使用文件名参数
    • 这些函数采取了额外的步骤来以通常的方式处理SIGPIPE (安静地终止,退出代码为141 ),而不是打破喧闹。 将function输出pipe道配pipe到提前closures的pipe道时(例如pipe道到达pipe道时),这一点非常重要。
  • 性能比较


  • 基于awksortcut 的符合POSIX标准的函数,根据OP自己的回答改编:
 shuf() { awk 'BEGIN {srand(); OFMT="%.17f"} {print rand(), $0}' "$@" | sort -k1,1n | cut -d ' ' -f2-; } 
  • 基于Perl的函数 – 改编自Moonyoung Kang的答案 :
 shuf() { perl -MList::Util=shuffle -e 'print shuffle(<>);' "$@"; } 
  • 基于Python的函数,改编自scai的答案 :
 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的回答 :
 shuf() { ruby -e 'Signal.trap("SIGPIPE", "SYSTEM_DEFAULT"); puts ARGF.readlines.shuffle' "$@"; } 

性能比较:

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

input文件是使用seq -f 'line %.0f' 1000000生成的1百万行文件
时间按升序排列(最快的):

  • shuf
    • 0.090s
  • Ruby 2.0.0
    • 0.289s
  • Perl 5.18.2
    • 0.589s
  • python
    • 1.342s与Python 2.7.6; 2.407s (!)与Python 3.4.2
  • awk + sort + cut
    • 3.003s与BSD awk ; GNU awk 2.388s (4.1.1); (1.3.4);

为了进一步比较,这些解决scheme未被封装为上述function:

  • sort -R (如果有重复的input行,则不是真正的随机播放)
    • 10.661s – 分配更多的内存似乎没有什么差别
  • 斯卡拉
    • 24.229s
  • bash循环+ sort
    • 32.593s

结论

  • 如果可以的shuf ,使用shuf – 这是迄今为止最快的。
  • Ruby做得很好,接下来是Perl
  • Python明显比Ruby和Perl慢,并且比较Python版本,2.7.6比3.4.1快很多
  • 作为最后的手段使用符合POSIX标准的awk + sort + cut组合 ; 你使用哪个awk实现( mawk比GNU awk快,BSD awk最慢)。
  • 远离sort -Rbash循环和Scala。

我使用一个小的perl脚本,我称之为“unsort”:

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

我也有一个NULL分隔的版本,称为“unsort0”…方便使用find -print0等。

PS:我也注意到了'shuf',我不知道coreutils这些日子里是否有这个function……如果你的系统没有'shuf',上面的内容可能仍然有用。

这是第一次在编码器上很容易,但是在CPU上很难,它在每一行上都有一个随机数,对它们进行sorting,然后从每一行中去除随机数。 实际上,这些行是随机sorting的:

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

这里是一个awk脚本

 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 

产量

 $ 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的一行代码:

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

而对于只打印一个随机行:

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

但看到这个post的python的random.shuffle()的缺点。 很多(超过2080年)的元素都不能很好地工作。

简单的基于awk的函数将完成这项工作:

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

用法:

 any_command | shuffle 

这应该适用于几乎所有的UNIX。 在Linux,Solaris和HP-UX上进行testing。

更新:

请注意,前导零( %06d )和rand()乘法使其在sort不理解数字的系统上也能正常工作。 它可以通过字典顺序排列(又称普通string比较)。

一个基于scai的答案 Python的class轮,但a)采取标准input,b)使结果重复与种子,c)挑出所有行中只有200。

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

Ruby FTW:

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

这是一个Python脚本,我保存为我的家庭文件夹中的rand.py:

 #!/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不可用,因此您可以在shuf其别名为:

 alias shuf='python rand.py' 

我们有一个包可以做这个工作:

 sudo apt-get install randomize-lines 

例:

创build一个有序的数字列表,并将其保存到1000.txt:

 seq 1000 > 1000.txt 

洗牌,简单地使用

 rl 1000.txt 

如果像我这样来到这里寻找一个替代macOS的shuf ,然后使用randomize-lines

安装randomize-lines (homebrew)软件包,它有一个与shuf类似的function的rl命令。

brew install randomize-lines

 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 

如果你已经安装了Scala,下面是一个简单的input框:

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

这个bash函数有最小的依赖(只有sorting和bash):

 shuf() { while read -rx;do echo $RANDOM$'\x1f'$x done | sort | while IFS=$'\x1f' read -rxy;do echo $y done } 

在Windows中你可以尝试这个batch file来帮助你洗牌你的data.txt,批处理代码的用法是

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

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

希望这可以帮助。

尚未提及:

  1. unsort util。 语法(有点播放列表):

     unsort [-hvrpncmMsz0l] [--help] [--version] [--random] [--heuristic] [--identity] [--filenames[=profile]] [--separator sep] [--concatenate] [--merge] [--merge-random] [--seed integer] [--zero-terminated] [--null] [--linefeed] [file ...] 
  2. msort可以按msort洗牌,但通常是过度的:

     seq 10 | msort -jq -b -l -n 1 -cr