Contents

计算文本文件中的重复行数

1. 概述

当我们使用 Linux 命令行时,处理文本文件是一种常见的操作。有时,我们可能会遇到包含重复行的文本文件。在本教程中,我们将学习如何计算文本文件中的重复行数。

2. 问题介绍

为了更容易解释如何计算重复行,让我们创建一个示例文本文件input.txt

$ cat input.txt
I will choose MAC OS.
I will choose Linux.
I will choose MAC OS.
I will choose Linux.
I will choose MAC OS.
I will choose Linux.
I will choose Linux.
I will choose Microsoft Windows.
I will choose Linux.
I will choose Linux.

如上面的输出所示,input.txt包含重复的行。接下来,我们要计算每一行的出现次数。

在本教程中,我们将介绍两种解决问题的方法:

  • 一起使用 *sort *命令和 /uniq 命令
  • 使用 awk 命令

之后,我们将比较这两种方法,并讨论哪一种方法能更好地解决问题。

3. 结合 sort命令和 uniq命令

** uniq命令有一个方便的-c选项来计算输入文件中出现的次数**。这正是我们正在寻找的。

但是,我们必须记住的一件事是带有*-c选项的uniq*命令仅在重复行相邻时才有效。也就是说,我们首先需要以某种方式将重复的行组合在一起。sort命令可以帮助我们解决这个问题。

让我们首先对input.txt进行排序,然后使用 -c选项将结果通过管道传输到uniq

$ sort input.txt | uniq -c
      6 I will choose Linux.
      3 I will choose MAC OS.
      1 I will choose Microsoft Windows.

如输出所示,每行的出现次数与行一起打印。问题已经解决了。

4. 使用awk命令

或者,我们可以使用非常简单的 awk 来解决这个问题:

$ awk '{ a[$0]++ } END{ for(x in a) print a[x], x }' input.txt 
1 I will choose Microsoft Windows.
6 I will choose Linux.
3 I will choose MAC OS.

我们可以在上面的输出中看到awk 也解决了这个问题。

现在,让我们了解一下 awk代码是如何工作的:

  • { a[$0]++ }:我们创建了一个关联数组(a[KEY])来记录行和出现的次数。KEY是输入文件中的一行,而值 a [KEY]是 KEY 出现的次数
  • END{ for(x in a) print a , x }:处理完所有行后,我们使用END打印出数组中的所有元素

5. 比较两种解决方案

使用sortuniq命令的解决方案很方便。同样,awk解决方案也非常简单。我们可能想问,哪个是更好的解决方案?

在本节中,让我们从性能、灵活性和可扩展性方面来比较这两种解决方案

5.1. 创建更大的输入文件

由于我们的input.txt只有十行,因此这两种方法都非常快速地解决了问题。

为了更好地比较两种解决方案的性能,我们将使用一个简单的 shell 脚本create_input.sh生成一个更大的输入文件:

#!/bin/sh
# the output file
BIG_FILE="big_input.txt"
# total number of lines
TOTAL=1000000
# an array to store lines
ARRAY=(
    "I will choose Linux."
    "I will choose Microsoft Windows."
    "I will choose MAC OS."
    )
# remove the file
rm -f "$BIG_FILE"
while (( TOTAL > 0 )) ; do
    echo ${ARRAY[$(( $RANDOM % 3 ))]} >> $BIG_FILE
    (( TOTAL-- ))
done

在上面的脚本中,我们将三行保存在一个名为ARRAY的 Bash 数组中。然后,在while循环中,我们从数组中随机选择一行并写入一个名为big_input.txt的文件。

如果我们执行脚本,我们将得到一个有一百万行的文件:

$ wc -l big_input.txt 
1000000 big_input.txt

接下来,我们将把这个文件作为输入来比较我们两种解决方案的性能。

5.2. 表现

让我们将每个解决方案应用于这个更大的输入文件,使用time 命令来测量它们的执行时间。 首先,让我们测试一下sort | uniq命令:

$ time (sort big_input.txt | uniq -c)
 333814 I will choose Linux.
 333577 I will choose MAC OS.
 332609 I will choose Microsoft Windows.
real	0m0.766s
user	0m1.995s
sys	0m0.053s

接下来,我们将测试awk命令:

$ time awk '{a[$0]++}END{for(x in a)print a[x], x}' big_input.txt
333814 I will choose Linux.
333577 I will choose MAC OS.
332609 I will choose Microsoft Windows.
real	0m0.190s
user	0m0.182s
sys	0m0.001s

上面的测试结果清楚地表明**,  awk命令比sortuniq的组合快得多(在这台机器上大约快四倍)**。这是因为:

  • awk命令只启动一个 进程,但 sort | uniq方法需要两个过程
  • awk命令只遍历文件一次,但是 sort | uniq组合必须两次处理输入文件中的所有行
  • sort命令将另外对文件进行排序; 因此,复杂度高于awk命令:O( nLog(n) ) > O(n)

5.3. 灵活性和可扩展性

uniq -c命令很方便。 但是,输出的格式是固定的。如果我们想调整输出,我们必须求助于其他文本处理实用程序。此外,这增加了更多的进程,并且输出将被处理更多次。

另一方面,我们可以使用awk命令自由控制输出的格式。

例如,让我们将计数放在每一行之后:

$ awk '{ a[$0]++ } END{ for(x in a) printf "%s [ count: %d ]\n", x, a[x] }' input.txt
I will choose Microsoft Windows. [ count: 1 ]
I will choose Linux. [ count: 6 ]
I will choose MAC OS. [ count: 3 ]

此外,得益于强大的awk语言,我们可以轻松扩展awk命令来处理更复杂的需求

例如,如果我们只想输出重复超过 3 次的行:

$ awk '{ a[$0]++ } END{ for(x in a) if(a[x]>3) print a[x], x }' input.txt 
6 I will choose Linux.

或者,如果我们想获得更详细的报告:

$ awk '{ a[$0]++ } END{ for(x in a) printf "%.2f%% (%d in %d): %s\n",100*a[x]/NR,a[x],NR, x }' input.txt
10.00% (1 in 10): I will choose Microsoft Windows.
60.00% (6 in 10): I will choose Linux.
30.00% (3 in 10): I will choose MAC OS.