在Linux中的文件中随机分组
1. 概述
在 Linux 中,重新排列文本文件中的行是一种常见的操作。有时,我们想按照特定的要求顺序重新排列这些行。sort 命令可以帮助我们做到这一点。
然而,有时我们想随机化文件中的行——换句话说,打乱文件中的行。
在本教程中,我们将看到在文本中打乱行的不同方法。此外,我们将比较这些方法并讨论它们的优缺点。
2. 输入文件示例
在我们开始洗牌文件之前,让我们首先准备一个文本文件作为后面部分中所有示例的输入:
$ cat input.txt
The original line number: 1
The original line number: 2
The original line number: 3
The original line number: 4
The original line number: 5
The original line number: 6
The original line number: 7
A line with some text.
A line with some text.
A line with some text.
如上面的输出所示,我们创建了一个名为*input.txt 的文件,*其中包含十行。在前七行中,我们在每行中放置了原始的行号,这样我们可以很容易地看到洗牌后的结果。
值得一提的是,最后三行是重复的,这意味着它们包含相同的文本。我们添加这三行是因为我们想观察打乱后的结果中重复行的分布。
在本教程中,让我们以三种方式随机播放输入文件:
- 使用shuf 命令
- 使用排序命令
- 使用随机数生成器和排序命令
3. 使用shuf命令
shuf实用程序是GNU Coreutils 包的成员。它输出输入行的随机排列。
** shuf命令 会在洗牌过程中将所有输入数据加载到内存中,如果输入文件大于可用内存,则该命令将不起作用。**
使用此命令随机排列文件中的行非常简单:
$ shuf input.txt
The original line number: 7
The original line number: 4
A line with some text.
The original line number: 1
A line with some text.
The original line number: 5
A line with some text.
The original line number: 3
The original line number: 6
The original line number: 2
上面的输出显示行是随机重新排序的,包括包含相同文本的三行。
如果我们多次运行该命令,每次都会得到不同的结果。
4. 使用sort命令
大多数时候,我们使用sort命令以某种预定义的顺序重新排列文件中的行。但是,我们可以使用 sort命令和选项-R*来进行随机排序*:
$ sort -R input.txt
A line with some text.
A line with some text.
A line with some text.
The original line number: 3
The original line number: 6
The original line number: 1
The original line number: 2
The original line number: 5
The original line number: 4
The original line number: 7
上面的输出显示,正如我们预期的那样,这些行是随机重新排列的。但是如果我们检查相同文本的三行,我们会发现它们是连续的。
好吧,这可能是偶然发生的。让我们再次运行命令,看看会发生什么:
$ sort -R input.txt
The original line number: 6
The original line number: 5
A line with some text.
A line with some text.
A line with some text.
The original line number: 7
The original line number: 1
The original line number: 4
The original line number: 3
The original line number: 2
这一次,输出中的行顺序与第一次运行不同。但是,同文的三行还是在一起。
事实上,无论我们运行命令多少次,这三行在输出中始终是连续的。这是**因为sort命令将为每一行计算一个散列键 ,然后根据生成的散列键进行排序。**如果输入行相同,则相同的散列函数也将产生相同的散列键。因此,这三行在我们的输出中总是在一起。
5. 三步法
除了 shuf和 sort -R 之外,我们还可以自己对文件中的行进行打乱。正如标题所示,我们将分三步进行:
- 在每行上添加一个随机数作为前缀。
- 按前缀编号对行进行排序。
- 去掉前缀。
5.1. 获取随机数
在 Linux 中生成随机数是一个有趣且相对复杂的话题。在本教程中,我们不会深入分析随机数生成。相反,我们将只介绍几种在 Linux 中获取随机数的方法。
在 Linux 中获取随机数的最直接方法是读取*$RANDOM* “变量”:
$ echo $RANDOM
12587
$ echo $RANDOM
26089
$ echo $RANDOM
17442
事实上,$RANDOM是一个内部 Bash 函数 ,它生成一个随机签名的 16 位整数(介于 0 和 32767 之间)。
另一种获取随机数的方法是使用设备/dev/random 。 ** /dev/random是一个特殊的设备文件。它使用从设备驱动程序和其他来源收集的噪声来生成随机数据。我们可以使用*od *(八进制转储)命令提取一些字节并打印它们的十进制等效值:**
$ od -An -N2 -i /dev/random
25824
$ od -An -N2 -i /dev/random
56167
$ od -An -N2 -i /dev/random
42527
** /dev/random也是内核的随机数生成器。它可以生成一个 4096 位的随机整数**:
$ cat /proc/sys/kernel/random/poolsize
4096
我们还可以通过一些高级脚本语言,如 Python、Perl 或awk 来获取随机数:
$ awk 'BEGIN{srand();for(i=1;i<=3;i++)print rand()}'
0.714734
0.174336
0.369674
5.2. 组装命令
最后,我们需要像这样构建命令:
cmd-to-prepend-random-number-on-each-line | sort -n -k 1 | cmd-to-remove-random-number-prefix-from-each-line
中间的sort命令按第一个字段对准备好的行进行排序,该字段包含我们预先设置的随机数。
让我们使用强大的awk命令在每一行前面加上一个随机数:
$ awk 'BEGIN{srand()}{print rand(), $0}' input.txt
0.118435 The original line number: 1
0.277674 The original line number: 2
0.139113 The original line number: 3
0.351707 The original line number: 4
0.178648 The original line number: 5
0.128693 The original line number: 6
0.625488 The original line number: 7
0.179445 A line with some text.
0.100277 A line with some text.
0.584702 A line with some text.
要删除我们的随机数前缀,我们可以删除每行开头的文本,直到第一个空格(包括)。我们有很多选择可以做到这一点。我们仍将使用awk命令来完成这项工作。
现在,让我们一起构建这三个部分:
$ awk 'BEGIN{srand()}{print rand(), $0}' input.txt \
| sort -n -k 1 \
| awk 'sub(/\S* /,"")'
The original line number: 4
A line with some text.
The original line number: 1
The original line number: 2
A line with some text.
The original line number: 7
A line with some text.
The original line number: 6
The original line number: 3
The original line number: 5
上面的结果显示所有行,包括三个重复的行,都是随机重新排列的。
6. 性能比较
到目前为止,我们已经看到了三种不同的方式来打乱文件中的行。我们可能会问,哪个更快?
让我们首先使用*seq *命令创建一个大文件来进行性能测试:
$ seq -f "The original line number: %g" 3000000 > big.txt
$ ls -l big.txt
-rw-r--r-- 1 kent kent 104M May 23 23:06 big.txt
$ wc -l big.txt
3000000 big.txt
如上面的输出所示,我们创建了一个名为big.txt 的文件,其中包含三百万行。 现在,我们将使用*time *命令测试比较不同方法的性能。
首先,让我们测试shuf命令:
$ time shuf big.txt > result.txt
real 0.73
user 0.64
sys 0.08
现在,sort -R方法:
$ time sort -R big.txt > result.txt
real 35.73
user 117.17
sys 0.17
最后,我们的三步法:
$ time awk 'BEGIN{srand()}{print rand(), $0}' big.txt \
| sort -n -k 1 \
| awk 'sub(/\S* /,"")' > result.txt
real 2.99
user 1.67
sys 0.05
结果表明sort -R方法比使用shuf 的方法慢很多(大约 50 倍)。这是因为sort命令必须为每一行计算一个散列键,这是一个非常昂贵的操作,而shuf命令没有这个计算。此外,shuf在内存中完成所有工作。
我们的三步解决方案也非常快。但是,它仍然比shuf方法慢四倍左右。这是因为三步法启动了三个进程并读取了三次大输入。
7. 优点和缺点
性能测试结束后,是时候讨论这三种方法的优缺点了。
7.1. shuf命令
优点:
- 直截了当
- 快速,因为所有处理都在内存中完成
- 重复的行也可以洗牌
缺点:
- 文件大小受限于可用内存量
当我们需要对文件进行 shuffle,并且文件可以加载到内存中时,shuf命令将是我们的首选。
但是,如果我们要使用shuf命令对一个大于内存大小的大文件的行进行打乱,我们可能必须先将其*拆分 *成小文件,然后在打乱后合并它们。
7.2. sort -R方法
优点:
- 直截了当
- 对文件大小没有限制
缺点:
- 由于哈希计算机制,大文件速度慢
- 重复的行总是粘在一起
7.3. 三步法
优点:
- 相当快
- 重复的行也可以洗牌
- 对文件大小没有限制
- 灵活且可扩展
缺点:
- 该脚本比其他解决方案更复杂
- 启动三个进程,对输入数据进行三次处理
当我们需要对一个无法完全加载到内存中的巨大文件进行 shuffle 时,这种方法比shuf命令更简单,并且比sort -R方法快得多 。