如何在Unix命令行或Shell脚本中对文本文件的行进行混编?


285

我想随机调整文本文件的行并创建一个新文件。该文件可能有数千行。

我怎样才能做到这一点与catawkcut等?



是的,在原始问题中还有其他一些不错的答案。
Ruggiero Spearman,2010年

那么,您是否正在制作wpa单词表?(只是一个随机的猜测)
thahgr 2015年

Answers:


360

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

正如jleedev指出的那样:sort -R也可能是一个选择。至少在某些系统上;好吧,你知道了。有人指出,sort -R并不是真正的洗牌,而是根据其哈希值对项目进行排序。

[编者注:除了重复的行/排序键总是彼此相邻结束之外,sort -R 几乎都改组了。换句话说:只有使用唯一的输入行/键,它才是真正的随机播放。确实,输出顺序是由哈希值决定的,但随机性来自选择随机哈希函数 -参见手册


31
shufsort -R略有不同,因为sort -R根据元素的哈希值对元素进行随机排序,也就是说sort -R会将重复的元素放在一起,而shuf将所有元素随机洗牌。
SeMeKh 2012年

146
对于OS X用户:brew install coreutils,然后使用gshuf ...(:
ELLIOTTCABLE 2013年

15
sort -R并且shuf应该被视为完全不同。sort -R是确定性的。如果您在相同的输入上在不同的时间两次调用它,您将得到相同的答案。shuf另一方面,会产生随机输出,因此很可能会在同一输入上提供不同的输出。
EfForEffort 2013年

18
那是不对的。每次调用“ sort -R”时,它都会使用不同的随机哈希键,因此每次生成的输出都不同。
马克·佩蒂特

3
关于随机性的说明:根据GNU文档,“默认情况下,这些命令使用内部的伪随机数生成器,该生成器由少量的熵初始化,但是可以通过--random-source = file选项直接使用外部源。”
罗伊斯·威廉姆斯

85

Perl单线将是Maxim解决方案的简单版本

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

6
我将其别名化以在OS X上随机播放。谢谢!
Unfun Cat 2014年

这是此页面上唯一返回REAL随机行的脚本。其他awk解决方案通常会打印重复输出。
菲利佩·阿尔瓦雷斯

1
但是要小心,因为在输出中您将丢失一行:)它只会与另一行连接在一起:)
JavaRunner 2014年

@JavaRunner:我假设您在谈论输入时不要拖尾\n;是的,\n必须存在-通常 -否则您将得到您所描述的内容。
mklement0

1
简明扼要。我建议替换<STDIN><>,因此该解决方案也适用于文件输入。
mklement0

60

该答案通过以下方式补充了许多现有的出色答案:

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

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


shuf() { awk 'BEGIN {srand(); OFMT="%.17f"} {print rand(), $0}' "$@" |
               sort -k1,1n | cut -d ' ' -f2-; }
shuf() { perl -MList::Util=shuffle -e 'print shuffle(<>);' "$@"; }
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))
' "$@"; }

有关此功能的Windows版本,请参见底部。

shuf() { ruby -e 'Signal.trap("SIGPIPE", "SYSTEM_DEFAULT");
                     puts ARGF.readlines.shuffle' "$@"; }

性能比较:

注意:这些数字是在2012年末配备3.2 GHz Intel Core i5和运行OSX 10.10.3的Fusion Drive的iMac上获得的。尽管时间会随所使用的OS,机器规格和awk所使用的实现而变化(例如,awkOSX上使用的BSD 版本通常比GNU慢awk,尤其是mawk),但这应该提供相对性能的一般含义

输入文件是一个1百万行文件与产生seq -f 'line %.0f' 1000000
时间以升序排列(最快的是第一个):

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

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

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

结论

  • 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。

Windows版本的Python解决方案(除了引号和与信号相关的语句的删除(Windows不支持)之外,Python代码是相同的):

  • 对于PowerShell(在Windows PowerShell中,$OutputEncoding如果要通过管道发送非ASCII字符,则必须进行调整):
# Call as `shuf someFile.txt` or `Get-Content someFile.txt | shuf`
function shuf {
  $Input | python -c @'
import sys, random, fileinput;
lines=[line for line in fileinput.input()];
random.shuffle(lines); sys.stdout.write(''.join(lines))
'@ $args  
}

请注意,PowerShell可以通过其Get-Randomcmdlet在本地进行改组(尽管性能可能会成为问题);例如:
Get-Content someFile.txt | Get-Random -Count ([int]::MaxValue)

  • 对于cmd.exe(批处理文件):

保存到文件shuf.cmd,例如:

@echo off
python -c "import sys, random, fileinput; lines=[line for line in fileinput.input()]; random.shuffle(lines); sys.stdout.write(''.join(lines))" %*

SIGPIPE在Windows上不存在,所以我改用了这种简单的python -c "import sys, random; lines = [x for x in sys.stdin.read().splitlines()] ; random.shuffle(lines); print(\"\n\".join([line for line in lines]));"
单线

@elig:谢谢,但是省略from signal import signal, SIGPIPE, SIG_DFL; signal(SIGPIPE, SIG_DFL);原始解决方案就足够了,并且保留了能够传递文件名参数的灵活性-无需更改其他任何内容(除了引号)-请参阅我在底部。
mklement0

27

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

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

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

PS:也投票赞成“ shuf”,我不知道这些天在coreutils中有没有……如果您的系统没有“ shuf”,则上面的内容可能仍然有用。


不错,RHEL 5.6没有shuf(
Maxim Egorushkin 2011年

1
做得很好; 我建议替换<STDIN><>,以使解决方案也可以使用文件中的输入。
mklement0

20

这是第一次尝试,在编码器上很容易,但是在CPU上却很困难,它将随机数添加到每行,对它们进行排序,然后从每行中剥离随机数。实际上,这些行是随机排序的:

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

8
UUOC。将文件传递给awk本身。
ghostdog74 2010年

1
正确,我使用进行调试head myfile | awk ...。然后我将其更改为cat;这就是为什么它留在那里的原因。
Ruggiero Spearman 2010年

不需要-k1 -n排序,因为awk的输出rand()是介于0和1之间的小数,并且所有重要的是它以某种方式被重新排序。-k1尽管rand()的输出应该足够唯一以使比较短路,但它可能会通过忽略其余部分来帮助加快速度。
bonsaiviking 2014年

@ ghostdog74:猫的大多数所谓的无用用法实际上对于在管道命令之间保持一致很有用,而不是保持一致。保留cat filename |(或< filename |)比记住每个程序如何(或不)获取文件输入要好。
ShreevatsaR

2
shuf(){awk'BEGIN {srand()} {print rand()“ \ t” $ 0}“” $ @“ | 排序 cut -f2-;}
喵喵,

16

这是一个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

做得不错,但实际上比OP自己的答案慢得多,后者结合awksortcut。对于不超过数千行,它并没有太大的区别,但是行数越高,它就越重要(阈值取决于所使用的awk实现)。轻微的简化将代替线while (1){if (e==d) {break}while (e<d)
mklement0

11

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

但是请参阅这篇文章以了解python的缺点random.shuffle()。它不适用于许多(超过2080个)元素。


2
“缺点”并非特定于Python。可以通过将PRNG与系统中的熵重新混合到PRNG中来解决有限的PRNG时期/dev/urandom。若要从Python的使用它:random.SystemRandom().shuffle(L)
jfs 2014年

join()是否不需要在'\ n'上,所以每行都单独打印?
elig

@elig:否,因为.readLines()返回带有尾随换行符的行。
mklement0

9

简单的基于awk的函数将完成此任务:

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

用法:

any_command | shuffle

这在几乎所有的UNIX上都可以使用。在Linux,Solaris和HP-UX上进行了测试。

更新:

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


将OP自己的答案打包为函数的好主意;如果追加"$@",它也可以作为输入使用文件。没有理由乘以rand(),因为sort -n它可以对小数点排序。但是,控制awk输出格式是个好主意,因为使用默认格式时%.6grand()将以指数形式输出偶数。在实践中,改组多达100万条线路可以说是足够的,但是在不付出很多性能损失的情况下,轻松支持更多线路是很容易的。例如%.17f
mklement0 2015年

1
@ mklement0我在写我的时没有注意到OP的回答。据我所知,rand()乘以10e6使其与solaris或hpux排序一起使用。有好主意“$ @”
米哈尔Šrajer

1
知道了谢谢; 也许您可以将乘法的基本原理添加到答案本身中;通常,根据POSIX,sort应该能够处理小数部分(即使有数千个分隔符,正如我刚刚注意到的那样)。
mklement0

7

Ruby FTW:

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

1
好东西; 如果使用puts ARGF.readlines.shuffle,则可以使其与stdin输入和文件名参数一起使用。
mklement0

甚至更短ruby -e 'puts $<.sort_by{rand}'-ARGF已经可以枚举,因此我们可以通过按随机值对行进行排序来对行进行混洗。
阿库恩

6

一种基于scai的答案的 Python衬里,但是a)接受标准输入,b)使结果与种子可重复,c)仅挑选出所有行中的200条。

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

6

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

例:

假设words.txt为:

the
an
linux
ubuntu
life
good
breeze

要随机排列,请执行以下操作:

$ shuf words.txt

这将乱码的线投向标准输出 ; 因此,您必须将其通过管道传输到类似以下的输出文件

$ shuf words.txt > shuffled_words.txt

一种这样的随机运行可以产生:

breeze
the
linux
an
ubuntu
good
life

4

我们有一个软件包可以完成这项工作:

sudo apt-get install randomize-lines

例:

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

seq 1000 > 1000.txt

随机播放,只需使用

rl 1000.txt

3

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

#!/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_profile作为别名此:

alias shuf='python rand.py'

3

如果像我一样,您是来这里寻找shufmacOS 的替代产品,然后使用randomize-lines

安装randomize-lines(自制程序)软件包,该软件包rl具有与相似的功能的命令shuf

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

1
使用安装Coreutils时brew install coreutils提供的shuf二进制文件为gshuf
shadowtalker

2

如果您已安装Scala,则可以使用以下方法来随机排列输入:

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

非常简单,但是除非必须启动Java VM,否则启动成本相当可观。在行数较多的情况下,效果也不佳。
mklement0

1

这个bash函数具有最小的依赖性(仅sort和bash):

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

不错的bash解决方案,与OP自己的awk辅助解决方案相似,但是对于较大的输入,性能将是一个问题;您对单个$RANDOM值的使用最多只能正确混洗多达32,768条输入行;尽管可以扩展该范围,但可能不值得:例如,在我的机器上,在32,768条短输入行上运行脚本大约需要1秒,这是运行shuf时间的150倍 ,大约10-15倍只要OP自己的awk辅助解决方案即可。如果您可以依靠sort在场,awk那么也应该在那里。
mklement0

0

在Windows中,您可以尝试使用此批处理文件来帮助您重新整理data.txt,批处理代码的用法是

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

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

希望这可以帮助。


不适用于大文件。2小时后,我放弃了一个超过100万行的文件
Stefan Haberl,2014年

0

截至目前尚未提及:

  1. unsortUTIL。语法(某种程度上面向播放列表):

    unsort [-hvrpncmMsz0l] [--help] [--version] [--random] [--heuristic]
           [--identity] [--filenames[=profile]] [--separator sep] [--concatenate] 
           [--merge] [--merge-random] [--seed integer] [--zero-terminated] [--null] 
           [--linefeed] [file ...]
  2. msort 可以按行随机播放,但这通常是过大的:

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

0

另一个awk变体:

#!/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 = "\n";
  srand();
}

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

END {
  for( k in lines ){
    print lines[k];
  }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.