使用命令行工具计算文件中每行的长度

98

问题

如果我有一个长文件,其中包含很多长度不同的行,如何计算每个行长度的出现次数?

示例:

file.txt

this
is
a
sample
file
with
several
lines
of
varying
length

运行count_line_lengths file.txt将会得到:

Length Occurences
1      1
2      2
4      3
5      1
6      2
7      2

有什么想法吗?


你怎么知道 length=1 是哪个单词?你也应该存储这个单词。 - Bill
语言:最好使用聪明的Shell命令。我可以轻松地在像Ruby或Python这样的语言中完成这个任务,但那样不够有趣 ;) - Pete Hamilton
@Bill 我并不是很关心单词,只关心行长度,除非我误解了你的问题? - Pete Hamilton
7个回答

139

这个命令使用 awk 计算每行的长度,然后使用 sort -n 对(数字类型的)行长进行排序,最后用 uniq -c 统计唯一的行长值。

$ awk '{print length}' input.txt | sort -n | uniq -c
      1 1
      2 2
      3 4
      1 5
      2 6
      2 7
在输出中,第一列是给定长度的行数,第二列是行长度。

在输出中,第一列是给定长度的行数,第二列是行长度。


96
更简洁的写法:awk '{print length}' input.txt | sort -n | uniq -c - Anders Johansson
2
不错的管道操作,但计数和去重可以很容易地在awk中完成。我想排序也可以在gawk中完成。我更喜欢纯bash解决方案。 - TrueY
15
我做了这个,但我们有非常长的行,并且默认情况下sort不能正确地排序数字(我得到了像1 9575 1 999这样的输出)。要正确地排序数字,请使用sort -g,将原始命令改为awk '{print length}' input.txt | sort -g | uniq -c - user82116
@user82116 我认为将sort命令替换为LC_ALL=C sort不仅可以正确地排序字符,而且速度更快。 - Hashim Aziz

31

纯 awk

awk '{++a[length()]} END{for (i in a) print i, a[i]}' file.txt

4 3
5 1
6 2
7 2
1 1
2 2

12

使用 bash 数组:

#!/bin/bash

while read line; do
    ((histogram[${#line}]++))
done < file.txt

echo "Length Occurrence"
for length in "${!histogram[@]}"; do
    printf "%-6s %s\n" "${length}" "${histogram[$length]}"
done

示例运行:

$ ./t.sh
Length Occurrence
1      1
2      2
4      3
5      1
6      2
7      2

1
@fedorqui 不过它并不是真正的可移植,所以根据使用情况,awk 胜出;-) 我发帖只是因为 OP 特别要求不涉及其他外部语言,这也有点意味着 awk(我是这样理解的)。好处是,如果你考虑 while read l;do((h[${#l}]++));done<file.txt;for l in "${!h[@]}";do echo "$l ${h[$l]}";done,它甚至没有那么长... - Adrian Frühwirth

9
$ perl -lne '$c{length($_)}++ }{ print qq($_ $c{$_}) for (keys %c);' file.txt

输出

6 2
1 1
4 3
7 2
2 2
5 1

2
对于高尔夫乐趣:perl -lnE '$c{+length}++}{say "$_ $c{$_}" for keys %c' - glenn jackman
4
我有一个包含一行极长(700-1000MB)的文件,在这里所有的单行命令中,只有这个没有崩溃。加1! - Randall Cook

2

试试这个:

awk '{print length}' FILENAME

如果您想要最长的长度,请使用“next”:

awk '{ln=length} ln>max{max=ln} END {print FILENAME " " max}'

您可以使用-exec选项将上述命令与find命令结合使用。

2
如果您允许交换列并且不需要标题,那么只需执行以下操作即可(无需使用sed或awk的高级技巧)。输出结果为:
1 1
2 2
3 4
1 5
2 6
2 7

需要牢记的一点是:wc -c计算的是字节数而不是字符数,对于包含多字节字符的字符串将无法给出正确的长度。因此应使用wc -m

参考资料:

uniq(1)手册

sort(1)手册

wc(1)手册


1
这不包括尾随空格。$line 需要加引号。 - Chris Noe

1
您可以仅使用基本的unix工具来完成此操作:
$ printf "%s %s\n" $(for line in $(cat file.txt); do printf $line | wc -c; done | sort -n | uniq -c | sed -E "s/([0-9]+)[^0-9]+([0-9]+)/\2 \1/")
1 1
2 2
4 3
5 1
6 2
7 2

它是如何工作的?

  1. 这是源文件:
    $ cat file.txt
    this
    is
    a
    sample
    file
    with
    several
    lines
    of
    varying
    length
    
  2. 用每行的长度替换源文件中的每一行:
    $ for line in $(cat file.txt); do printf $line | wc -c; done
    4
    2
    1
    6
    4
    4
    7
    5
    2
    7
    6
    
  3. 对长度进行排序并计算出现次数:
    $ for line in $(cat file.txt); do printf $line | wc -c; done | sort -n | uniq -c
          1 1
          2 2
          3 4
          1 5
          2 6
          2 7
    
  4. 交换并格式化数字:
    $ printf "%s %s\n" $(for line in $(cat file.txt); do printf $line | wc -c; done | sort -n | uniq -c | sed -E "s/([0-9]+)[^0-9]+([0-9]+)/\2 \1/") 
    1 1
    2 2
    4 3
    5 1
    6 2
    7 2
    

3
wc -c 计算的是字节而不是字符。如果你有多字节字符,你会得到更大的数字。尝试 echo -n "你好" | wc -cecho -n "你好" | wc -m 的区别。 - imrek
@DrunkenMaster 你说得对,我应该只需将 wc -c 替换为 wc -m 吗? - Maksym Ganenko
1
我认为现在阅读你的回答的任何人都能清楚了,只需参考上面的评论即可。 - imrek
请注意,带有空格的行会被分割,并且在 file.txt 中出现任何 % 字符都会导致意外结果。 - sampi

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