我想随机打乱文本文件的行顺序并创建一个新文件。该文件可能有几千行。
我可以如何使用cat
,awk
,cut
等进行操作?
我想随机打乱文本文件的行顺序并创建一个新文件。该文件可能有几千行。
我可以如何使用cat
,awk
,cut
等进行操作?
shuf
和 sort -R
有些微小的不同,因为 sort -R
根据元素的哈希值来随机排序,这意味着 sort -R
会把重复的元素放在一起,而 shuf
则是将所有元素随机打乱。 - semekhbrew install coreutils
安装核心工具,然后使用 gshuf ...
命令。 - ELLIOTTCABLEsort -R
和 shuf
应该被视为完全不同的命令。sort -R
是确定性的。如果你在相同的输入上在不同的时间调用它两次,你会得到相同的答案。而 shuf
则产生随机输出,因此在相同的输入上它很可能会给出不同的输出结果。 - EfForEffort使用 Perl 一行代码就可以简单地实现 Maxim 方案的版本
perl -MList::Util=shuffle -e 'print shuffle(<STDIN>);' < myfile
\n
输入;是的,那个\n
必须存在 - 而且通常也确实存在 - 否则你会得到你所描述的结果。 - mklement0<STDIN>
替换为<>
,这样解决方案也可以处理来自文件的输入。 - mklement0这个答案在以下方面补充了许多优秀的现有答案:
现有答案被打包成了灵活的shell函数:
SIGPIPE
(静默终止,退出码为141
),而不是嘈杂地中断。当将函数输出管道传输到早期关闭的管道时,例如管道传输到head
时,这一点非常重要。进行了性能比较。
awk
,sort
和cut
的符合POSIX标准的函数,改编自原作者的答案: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' "$@"; }
性能比较:
说明:这些数字是在配备 3.2 GHz Intel Core i5 处理器和 Fusion Drive 的2012年底的 iMac 上运行 OSX 10.10.3 时获取的。虽然计时会随着操作系统、机器规格和使用的 awk 实现而变化(例如,OSX 上使用的 BSD awk 版本通常比 GNU awk 和尤其是 mawk 更慢),但这应该提供了相对性能的一般感觉。
输入文件是使用 seq -f 'line %.0f' 1000000
生成的1百万行文件。
时间按升序排列(最快的第一项):
shuf
0.090s
0.289s
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.811s
(使用 mawk (1.3.4)); 进一步比较以下未打包为函数的解决方案:
sort -R
(如果有重复的输入行,则不是真正的洗牌)
10.661s
- 分配更多内存似乎没有什么区别24.229s
bash
循环 + 排序
32.593s
结论:
shuf
- 它是最快的。awk
+ sort
+ cut
组合方式;你所使用的 awk
实现很重要(mawk
比 GNU awk
快,BSD awk
最慢)。sort -R
、bash
循环和 Scala。Python 解决方案的 Windows 版本(Python 代码相同,只是引号和与信号相关的语句有所变化,因为这些语句在 Windows 上不受支持):
$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
命令本地洗牌(尽管性能可能存在问题)。例如:
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))" %*
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]));"
。 - eligfrom signal import signal, SIGPIPE, SIG_DFL; signal(SIGPIPE, SIG_DFL);
就足够了,并保留了还能传递文件名_参数_的灵活性 - 除了引用之外不需要更改任何内容 - 请参见我在底部添加的新部分。 - mklement0我使用一个小型的 Perl 脚本,我称之为 "unsort" :
#!/usr/bin/perl
use List::Util 'shuffle';
@list = <STDIN>;
print shuffle(@list);
我还有一个以空值为分隔符的版本,称为"unsort0"...在与find-print0等工具一起使用时非常方便。
另外,顺便点赞'shuf',我不知道现在在coreutils中已经有了它...如果你的系统没有'shuf',上面的内容仍然可能会有用。
<STDIN>
替换为<>
,以便使该解决方案能够处理来自文件的输入。 - mklement0这是一种对程序员来说比较简单但对CPU压力较大的尝试,它在每行前面添加一个随机数,然后对它们进行排序,并从每行中删除随机数。实际上,这些行是随机排序的。
cat myfile | awk 'BEGIN{srand();}{print rand()"\t"$0}' | sort -k1 -n | cut -f2- > myfile.shuffled
head myfile | awk ...
进行调试。然后我只是将它改成了 cat;这就是为什么它留在那里的原因。 - Ruggiero Spearmancat filename |
(或 < filename |
),而不是记住每个单独程序如何接受文件输入(或不接受)。 - ShreevatsaR-k1
是多余的,因为它仍然将整行作为一个整体进行排序,并没有指定停止字段;如果要真正限制排序到第一个字段,则需要使用 -k1,1
。然而,使用 -n
可以明显加快排序速度。
因此,您应该使用 -k1,1 -n
(以明确表述)或者利用排序键是第一个字段并且 sort
在检测数字时使用最长前缀匹配这一事实,只需使用 -n
。 - mklement0这是一个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
awk
与sort
和cut
结合使用。对于不超过几千行的情况,这没有太大区别,但对于更高的行数,它很重要(阈值取决于所使用的awk
实现)。稍微简化一下就是用while (e<d)
替换while (1){
和if (e==d) {break}
这两行。 - mklement0python -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个),那么其效果将不理想。
/dev/urandom
所做的那样。要从 Python 中利用它:random.SystemRandom().shuffle(L)
。 - jfs.readLines()
返回带有换行符的行。 - mklement0shuffle() {
awk 'BEGIN{srand();} {printf "%06d %s\n", rand()*1000000, $0;}' | sort -n | cut -c8-
}
使用方法:
any_command | shuffle
这个应该可以在几乎所有的UNIX系统上运行。已在Linux、Solaris和HP-UX上测试。
更新:
请注意,在一些不支持数字排序的系统上,前导零 (%06d
) 和 rand()
的乘法会使其正常工作。可以通过字典序(也称为普通字符串比较)进行排序。
"$@"
, 它也可以处理_文件_作为输入。没有理由去乘以 rand()
,因为 sort -n
可以排序十进制小数。但是,控制 awk
的输出格式是一个好主意,因为默认格式 %.6g
, rand()
会输出偶尔使用 指数 表示法的数字。
虽然在实践中随机排列高达100万行足以满足需求,但很容易支持更多行而不付出太多性能代价;例如%.17f
。 - mklement0一个简单直观的方法是使用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
胜利属于Ruby:
ls | ruby -e 'puts STDIN.readlines.shuffle'
puts ARGF.readlines.shuffle
,你可以让它同时适用于标准输入和文件名参数。 - mklement0ruby -e 'puts $<.sort_by{rand}'
— ARGF 已经是一个可枚举对象,因此我们可以通过按随机值排序来打乱行。 - akuhn
shuf
命令来随机化文件中的行。例如,要随机化名为file.txt
的文件中的行,可以运行以下命令:shuf file.txt -o file.txt
- Dennis Williamson