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

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

我怎么能做到与catawkcut等?

200350 次浏览

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

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

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

正如jleedev指出的:sort -R也可能是一个选项。至少在某些系统上;好吧,你懂的。这一点已被指出sort -R并不真正洗牌,而是根据它们的哈希值进行排序。

[编者注:sort -R 几乎洗牌,除了重复行/排序键总是以相邻结束。]换句话说:只有唯一输入行/键才是真正的shuffle。虽然输出顺序确实是由哈希值决定的,但随机性来自于选择随机哈希函数 -参见manual。]

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

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

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

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

PS:我也投票给了“shuf”,我不知道现在coreutils有这个词……如果您的系统没有'shuf',上述方法可能仍然有用。

Perl一行程序是Maxim解决方案的简单版本

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

简单的基于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不理解数字的系统上也能正常工作。它可以通过字典顺序(也就是普通字符串比较)进行排序。

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个)元素。

这是一个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是不可用的,所以你可以在你的bash_profile中别名为:

alias shuf='python rand.py'

一个基于脊髓的回答的Python行,但是a)接受stdin, b)使结果与seed可重复,c)只挑选出所有行的200行。

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

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

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

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

希望这能有所帮助。

如果你安装了Scala,这里有一行代码来洗牌输入:

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

Ruby增值:

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

这个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
}

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

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

    • 函数不仅取stdin输入,也取filename参数 .
    • 函数采取额外的步骤以通常的方式处理SIGPIPE(带有退出代码141的安静终止),而不是吵闹地中断。当将函数输出输送到一个提前关闭的管道时,这很重要,例如当输送到head时。
    • 李< / ul > < / >
    • 进行性能比较


    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年底的iMac上获得的,配有3.2 GHz英特尔酷睿i5和Fusion Drive,运行OSX 10.10.3。而时间将根据所使用的操作系统,机器规格,使用的awk实现而变化(例如,在OSX上使用的BSD awk版本通常比GNU awk,特别是mawk慢),这应该提供了相对性能的总体感觉

    输入文件是由seq -f 'line %.0f' 1000000生成的100万-线文件 时间按升序排列(最快的第一):

    • < >强shuf
      • 0.090s
      • 李< / ul > < / > <李> Ruby 2.0.0
        • 0.289s
        • 李< / ul > < / > <李> Perl 5.18.2
          • 0.589s
          • 李< / ul > < / >
          • < >强Python
            • 1.342s with Python 2.7.6;2.407s(!)使用Python 3.4.2
            • 李< / ul > < / >
            • awk + sort + cut
              • 3.003s与BSD awk;2.388s与GNU awk (4.1.1);1.811s with mawk (1.3.4);
              • 李< / ul > < / >

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

              • sort -R(如果有重复的输入行,则不是真正的shuffle)
                • 10.661s -分配更多的内存似乎没有什么不同
                • 李< / ul > < / >
                • < >强Scala
                  • 24.229s
                  • 李< / ul > < / >
                  • bash循环+ sort
                    • 32.593s
                    • 李< / ul > < / >

                    结论:

                    • 如果可以的话,使用shuf -这是目前为止最快的。
                    • Ruby很好,后面跟着Perl
                    • Python明显比Ruby和Perl慢,并且,比较Python版本,2.7.6比3.4.1快很多
                    • 最后使用posix兼容的__ABC0 + __ABC1 + cut组合;你使用的awk实现很重要(mawk比GNU awk快,BSD awk最慢)。
                    • 远离sort -Rbash循环和Scala。

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

                    • 对于PowerShell(在Windows PowerShell中,如果你想通过管道发送非ascii字符,你必须调整$OutputEncoding):
                    # 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-Random cmdlet本地shuffle(尽管性能可能是一个问题);例如:< br > Get-Content someFile.txt | Get-Random -Count ([int]::MaxValue) < / p >
                    • 对于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))" %*
                    

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

sudo apt-get install randomize-lines

例子:

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

seq 1000 > 1000.txt

要洗牌,只需使用

rl 1000.txt

如果你像我一样来这里为macOS寻找shuf的替代方案,那么使用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. 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 can shuffle by line, but it's usually overkill:

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

另一个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];
}
}

一个简单而直观的方法是使用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