在bash中查找文件中最常见的行

8

我有一个字符串文件:

string-string-123
string-string-123
string-string-123
string-string-12345
string-string-12345
string-string-12345-123

如何在bash中检索最常见的行(string-string-123)?

可能是在bash中查找文件中最频繁的行的重复问题。 - Ciro Santilli OurBigBook.com
3个回答

21

你可以在 sort 命令中使用 uniq 选项。

sort file | uniq -c | sort -n -r

5
您可以使用awk来实现此操作:
awk '{++a[$0]}END{for(i in a)if(a[i]>max){max=a[i];k=i}print k}' file

数组a用于记录每一行的计数。读完文件后,我们遍历数组并找到计数最多的那一行。
或者,在文件处理过程中可以通过赋值来跳过END块中的循环:
awk 'max < ++c[$0] {max = c[$0]; line = $0} END {print line}' file

感谢格伦·杰克曼提供这个有用的建议。


正如大家指出的那样,以上两种方法在出现并列最高次数的情况下只会打印其中一行。以下版本将打印所有并列最高次数的行:

awk 'max<++c[$0] {max=c[$0]} END {for(i in c)if(c[i]==max)print i}' file

2
将“max”逻辑从END块中移出以简化代码:awk '{if (max < ++c[$0]) {max = c[$0]; line = $0}} END {print line}' - glenn jackman
这是一个优雅的解决方案,但请注意,如果有多个最常出现的行,则您的解决方案只会打印其中的一个,并且不明显哪一个。 - mklement0
1
@mklement0 这是一个很好的观点。我已经添加了另一个版本,它会打印出所有的内容。 - Tom Fenech
感谢您的更新 - 做得很好。唯一剩下的问题是,鉴于您将所有不同的输入行读入内存,这种方法可能无法处理大型输入文件,这可能会成为输入中大量不同行的问题。但好的一面是,您的方法比“sort”/“uniq”方法快得多。 - mklement0
另一种方法是使用asort awk '{b[a[$0]++]=$0}END{asort(b);print b[1]}' - user4453924

5
  • Tom Fenech的优雅的awk答案在修订版中表现出色,可以打印所有最常出现的行,但是对于大文件可能不适用,因为所有不同的输入行都存储在关联数组中,这可能是一个问题,如果有许多非重复行;尽管如此,它比下面讨论的方法要快得多。

  • Grzegorz Żur的答案优雅地结合了多个实用程序来隐式地产生所需的结果,但是:

    • 打印所有不同的行(最高频率计数第一)
    • 输出行以其发生次数为前缀(实际上可能是可取的)。

虽然您可以将Grzegorz Żur's answer管道传输到head以限制显示的行数,但通常情况下不能假定有固定数量的行。

在Grzegorz的答案基础上,这里提供了一个通用的解决方案,它显示所有最常出现的行 - 不管有多少行 - 以及仅仅它们:

sort file | uniq -c | sort -n -r | awk 'NR==1 {prev=$1} $1!=prev {exit} 1'

如果您不想让输出行以出现次数为前缀:
sort file | uniq -c | sort -n -r | awk 'NR==1 {prev=$1} $1!=prev {exit} 1' | 
  sed 's/^ *[0-9]\{1,\} //'

Grzegorz Żur的回答的解释:

  • uniq -c 输出一组唯一的输入行,前缀为它们各自的出现次数-c),后跟一个空格。
  • sort -n -r 然后按数字排序结果行(-n),按降序排列(-r),以便最常出现的行位于顶部。
    • 请注意,如果未指定-k,则sort通常会尝试按整个输入行进行排序,但-n仅使用被识别为整数的最长前缀进行排序,这正是此处所需的。

awk命令的解释:

  • NR==1 {prev=$1} 将第一个空格分隔字段 ($1) 存储在变量 prev 中,用于处理输入的第一行 (NR==1)。
  • $1!=prev {exit} 如果第一个空格分隔字段与上一行不同,则终止处理 - 这意味着已到达非顶部行,并且不需要再打印更多行。
  • 1{ print } 的简写,表示应按原样打印正在处理的输入行。

sed命令的解释:

  • ^ *[0-9]\{1,\}匹配每个输出行的数字前缀(表示出现次数),如uniq -c最初生成的那样。
  • 应用s/...//意味着前缀被替换为一个空字符串,即有效地删除

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接