Contents

如何在文件中打印最长的行

1. 概述

当我们在 Linux 命令行中工作时,我们经常处理文本文件。在本教程中,我们将讨论如何找出文件中最长的行。

2. 问题介绍

首先,我们通过一个例子来理解问题:

$ cat lines.txt
Hi there,
How are you?
Recently I have to process many text files.
I like it.
Sometimes files could have very long lines.
My task is finding the longest lines.
For example, this is a really long... line.
I love Linux.
Bye

我们想从文件 lines.txt 中找到最长的行。

为了识别最长的行,让我们在每行的长度前加上一个短的awk 单行:

$ awk '{printf "%2d| %s\n",length,$0}' lines.txt
 9| Hi there,
12| How are you?
43| Recently I have to process many text files.
10| I like it.
43| Sometimes files could have very long lines.
37| My task is finding the longest lines.
43| For example, this is a really long... line.
13| I love Linux.
 0| 
 3| Bye

上面的输出显示文件中的最大行长度为43,三行具有此长度。我们的目标是找到这三个最长的行。

有时,我们可能不想拥有所有最长的行。我们可能有一些要求,例如:如果有多个最长的行,则打印其中的第一个或最后一个。

但是,一旦我们拥有所有最长的线路,就有很多方法可以选择第一条或最后一条。例如,我们可以使用headtail 命令来做到这一点。

因此,在本教程中,我们将研究两种从文件中查找所有最长行的方法:

  • 使用 *wc grep *命令
  • 使用 awk命令

现在,让我们看看如何解决这个问题。

3. 使用wcgrep

解决问题的一种方法是结合 wc和 grep命令。

我们知道grep命令可以使用文本文件中的正则表达式匹配模式。如果我们知道最长的行长度为MAX_LEN那么所有最长的行都应该匹配ERE 模式“ ^.{MAX_LEN}$ ”。

查找最大行长度将是wc命令的任务。

3.1. 使用wc命令的陷阱

我们先来看看wc命令。

** wc命令有一个选项-L (–max-line-length),我们可以使用它来打印最大行长度**:

$ wc -L lines.txt 
43 lines.txt

如输出所示,43lines.txt文件中的最大行长度——到目前为止,非常好。

但是,如果输入中有TABwc -L会让我们感到惊讶。让我们看一些例子:

$ echo -e "\t" | wc -L
8
$ echo -e "a\t" | wc -L
8
$ echo -e "abc\t" | wc -L
8
$ echo -e "abcde\t" | wc -L
8

这是因为wc -L打印最大显示宽度而不是最大行长度,即使 long 选项称为–max-line-length*也是如此。*

让我们在上面示例中的每个输入中附加一个“ $ ”字符,并检查它们是否具有相同的显示宽度:

$ echo -e "a\t$"
a       $
$ echo -e "ab\t$"
ab      $
$ echo -e "abc\t$"
abc     $
$ echo -e "abcde\t$"
abcde   $

** wc命令将 TAB 计为 8个字符的长度。到目前为止,没有任何选择可以改变它。**

如果我们想仔细看看它,我们可以在wc 源代码中找到TAB是如何处理的:

switch (wide_char)
   {
 ...
   case '\t':
     linepos += 8 - (linepos % 8);
 ...

在将输入传递给wc命令之前,我们可以使用tr 命令将TAB转换为空格来解决这个问题 :

$ echo -e "a\t" | tr '\t' ' ' | wc -L 
2
$ echo -e "abcde\t" | tr '\t' ' ' | wc -L 
6

因此,对于我们的问题,一个稳定的 wc命令应该是:

$ tr '\t' ' ' <lines.txt | wc -L
43

3.2. 组装wcgrep命令

现在我们可以组合wc -Lgrep命令来查找所有最长的行:

$ grep -E "^.{$(tr '\t' ' ' <lines.txt | wc -L)}$" lines.txt
Recently I have to process many text files.
Sometimes files could have very long lines.
For example, this is a really long... line.

好的!所有三个最长的行都被打印出来。

命令很简单。我们使用命令替换  $(tr ‘\t’ ‘ ‘ <lines.txt | wc -L) 来获得 wc命令的输出:43

4. 使用 awk命令

我们来看看awk是如何解决这个问题的:

$ awk '{ln=length}
       ln>max{delete result; max=ln}
       ln==max{result[NR]=$0} 
       END{for(i in result) print result[i] }' lines.txt
Recently I have to process many text files.
Sometimes files could have very long lines.
For example, this is a really long... line.

上面的输出显示了我们想要的三个最长的行。现在,让我们了解一下awk命令的工作原理:

  • *{ln=length} —这里 的长度 是**length($0)*的简写形式。我们将行长保存在一个变量中:  ln
  • ln>max{…} — 我们使用一个max变量来保存迄今为止最大的ln 。对于每个新的ln,我们将lnmax变量进行比较
  • ln>max{删除结果;max=ln} — 如果当前 ln大于 max,我们清空 结果数组,让 ln为 最大值
  • ln==max{result[NR]=$0} — 如果 ln == max表示当前是最长的行之一,我们将其添加到结果数组中
  • END{for(i in result) print result[i]} — 在 END 中,我们打印 结果数组中的所有元素

5. 基准性能

到目前为止,我们有两种不同的解决方案来解决我们的问题。我们可能想知道哪种方法更快。

在比较它们的性能之前,让我们创建一个带有很长行的更大的输入文件:

$ for n in {1..10}; do rand=$RANDOM;echo "$(tr -dc A-Za-z0-9 </dev/urandom | head -c$rand)" >>big.txt;done
$ file big.txt 
big.txt: ASCII text, with very long lines

我们通过读取*/dev/urandom*创建了一个包含 11 行的文本文件。让我们检查一下行长:

$ awk '{printf "line #%d has length:%d\n",NR,length}' big.txt
line #1 has length:0  
line #2 has length:4998
line #3 has length:5880
line #4 has length:9487
line #5 has length:18474
line #6 has length:28352
line #7 has length:4441
line #8 has length:6502
line #9 has length:21588
line #10 has length:5051
line #11 has length:15307

我们将使用*time *命令对这两种方法的性能进行基准测试。

首先,让我们测试一下wcgrep解决方案

$ time grep -E "^.{$(tr '\t' ' ' <big.txt | wc -L)}$" big.txt > /dev/null
real 4.12 
user 4.06
sys 0.05

我们已经等了四秒多才找到最长的线路。

现在,让我们看看awk解决方案是如何执行的:

$ time awk '{ln=length}ln>max{delete result; max=ln}
     ln==max{result[NR]=$0} END{for(i in result) print result[i] }' big.txt > /dev/null
real 0.00  
user 0.00
sys 0.00

awk命令立即完成*!*

测试表明,*awk解决方案比wcgrep*解决方案快得多。**这是因为:

  • wcgrep解决方案将通过输入三遍:tr(1)wc(1)grep(1),而awk命令只通过文件一次
  • trwc命令将检查输入中的 每个字符,这很昂贵
  • grep在每一行上进行正则表达式匹配,这也是一个相当昂贵的操作
  • 另一方面,awk命令只关注每一行的长度,而不查看每个字符