Contents

在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 之外,我们还可以自己对文件中的行进行打乱。正如标题所示,我们将分三步进行:

  1. 在每行上添加一个随机数作为前缀。
  2. 按前缀编号对行进行排序。
  3. 去掉前缀。

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方法快得多 。