如何计算每行中特定字符的数量?


87

我想知道如何通过某些文本处理实用程序计算每行中特定字符的数量?

例如,要计算"以下文本的每一行

"hello!" 
Thank you!

第一行有两个,第二行有0。

另一个示例是(在每一行中计数。


1
只是要补充一点,为此您编写了自己的10行C程序,而不是使用带有sed的正则表达式,从而大大提高了性能。您应该考虑根据输入文件的大小进行操作。
2011年

Answers:


104

您可以使用sed和进行操作awk

$ sed 's/[^"]//g' dat | awk '{ print length }'
2
0

datsed在示例文本的哪里,sed删除(对于每一行)所有非"字符,并awk为每一行打印其大小(即length等于length($0),其中$0表示当前行)。

对于另一个字符,您只需要更改sed表达式即可。例如用于(

's/[^(]//g'

更新: sed对于任务来说有点过头了- tr足够。的等效解决方案tr是:

$ tr -d -c '"\n' < dat | awk '{ print length; }'

意味着tr删除所有不在-c字符集中的字符(表示补码)"\n


3
+1应该比trwc版本更有效。
斯蒂芬·吉梅内斯

1
是的,但是它可以处理Unicode吗?
amphetamachine

@amphetamachine,是的-在Ubuntu 10.04系统上,至少使用ß(utf hex:c3 9f)(而不是")进行的快速测试可以按预期工作,即trsed并且awk可以毫无问题地进行补码/替换/计数。
maxschlepzig

1
大多数版本tr,包括GNU tr和经典Unix tr,都使用单字节字符,并且不兼容Unicode。.引自Wikipedia tr(Unix) ..尝试以下代码段:echo "aā⧾c" | tr "ā⧾" b...在Ubuntu 10.04上... ß是单字节扩展拉丁字符和被处理tr......真正的问题在这里不是tr不处理Unicode(因为所有字符都是Unicode),这是真的那么tr一次只能处理一个字节..
Peter.O

@fred,否,ß不是单字节字符-其Unicode位置为U + 00DF,在UTF-8中编码为“ c3 9f”,即两个字节。
maxschlepzig

49

我只会用awk

awk -F\" '{print NF-1}' <fileName>

在这里,我们将字段分隔符(带有-F标志)设置为字符,"然后我们要做的就是打印字段数NF-1。目标字符的出现次数将比分隔的字段数少一。

对于由Shell解释的有趣字符,您只需要确保将其转义即可,否则命令行将尝试对它们进行解释。因此,对于这两者")您都需要转义字段分隔符(带有\)。


1
也许编辑您的答案以使用单引号引起来。它可以与任何字符一起使用(除外')。此外,它具有空行的奇怪行为。
斯蒂芬·吉梅内斯

该问题专门用于此问题,"因此我认为必须使代码能够使用它。这取决于您所使用的外壳天气,角色需要逃脱,但bash / tcsh都需要逃脱“
马丁·约克

当然可以,但是没有问题-F'"'
斯特凡·吉梅内斯

+1使用FS的好主意。...这将解决显示-1的空白行,例如bash命令行中的“ $ 1”。...awk -F"$1" '{print NF==0?NF:NF-1}' filename
Peter.O 2011年

也可以使用多个字符作为分隔符...有用!
COil 2016年

14

使用trard wc

function countchar()
{
    while IFS= read -r i; do printf "%s" "$i" | tr -dc "$1" | wc -m; done
}

用法:

$ countchar '"' <file.txt  #returns one count per line of file.txt
1
3
0

$ countchar ')'           #will count parenthesis from stdin
$ countchar '0123456789'  #will count numbers from stdin

3
注意。tr不处理超过一个字节的字符.. 参见Wikipedia tr(Unix) ..即 tr不符合Unicode。
Peter.O 2011年


您需要从中删除空格字符$IFS,否则read将从头到尾修剪它们。
斯特凡Chazelas


@ Peter.O,某些tr实现支持多字节字符,但是wc -c对字节计数,而不是字符计数(需要wc -m字符)。
斯特凡Chazelas

11

然而,这不依赖于外部程序,在另一个实施bashzshyash和一些实现/版本ksh

while IFS= read -r line; do 
  line="${line//[!\"]/}"
  echo "${#line}"
done <input-file

使用line="${line//[!(]}"计数(


当最后一行没有尾随\ n时,while循环退出,因为尽管它读取了最后一行,但它还返回了一个非零的退出代码来表示EOF ... (..它一直困扰着我一段时间,而我刚刚发现了这个工作)... eof=false; IFS=; until $eof; do read -r || eof=true; echo "$REPLY"; done
Peter.O 2011年

@Gilles:您添加了/bash不需要的尾随。这是ksh的要求吗?
enzotib

1
/在较旧的ksh版本中需要尾随,在较旧的bash版本中也需要IIRC。
吉尔斯

10

awk如果匹配数太大(使用这种情况正是我的情况),则使用答案失败。对于loki-astari的回答,报告了以下错误:

awk -F" '{print NF-1}' foo.txt 
awk: program limit exceeded: maximum number of fields size=32767
    FILENAME="foo.txt" FNR=1 NR=1

对于enzotib的答案(以及来自manatwork的等效结果),发生了分段错误:

awk '{ gsub("[^\"]", ""); print length }' foo.txt
Segmentation fault

maxschlepzigsed解决方案可以正常工作,但速度较慢(下面的时序)。

这里还没有建议一些解决方案。首先,使用grep

grep -o \" foo.txt | wc -w

并使用perl

perl -ne '$x+=s/\"//g; END {print "$x\n"}' foo.txt

以下是一些解决方案的时间安排(从最慢到最快顺序排列);我把事情限制在这里。“ foo.txt”是一个包含一行和一个长字符串的文件,其中包含84922个匹配项。

## sed solution by [maxschlepzig]
$ time sed 's/[^"]//g' foo.txt | awk '{ print length }'
84922
real    0m1.207s
user    0m1.192s
sys     0m0.008s

## using grep
$ time grep -o \" foo.txt | wc -w
84922
real    0m0.109s
user    0m0.100s
sys     0m0.012s

## using perl
$ time perl -ne '$x+=s/\"//g; END {print "$x\n"}' foo.txt
84922
real    0m0.034s
user    0m0.028s
sys     0m0.004s

## the winner: updated tr solution by [maxschlepzig]
$ time tr -d -c '\"\n' < foo.txt |  awk '{ print length }'
84922
real    0m0.016s
user    0m0.012s
sys     0m0.004s

+好主意!我用新的答案扩展了表格,随时可以编辑(最终图片不太清楚,但是我相信@maxschlepzig是更快的解决方案)
JJoao 2015年

maxschlepzig的解决方案超级快!
okwap


8

使用awk和gsub的另一种可能的实现:

awk '{ gsub("[^\"]", ""); print length }' input-file

该功能gsub等效于sed的's///g'

使用gsub("[^(]", "")计数(


您可以保存一个字符,即在删除标准输入重定向时...;)
maxschlepzig

@maxschlepzig:是的,当然;)
enzotib

1
awk '{print gsub(/"/,"")}' input-file这样就足够了,因为“对于每个与字符串t中的正则表达式r匹配的子字符串,替换字符串s,并返回替换数目。” (man awk)
manatwork 2011年

6

我决定写一个C程序,因为我很无聊。

您可能应该添加输入验证,但是除此之外。

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
        char c = argv[1][0];
        char * line = NULL;
        size_t len = 0;
        while (getline(&line, &len, stdin) != -1)
        {
                int count = 0;
                char * s = line;
                while (*s) if(*s++ == c) count++;
                printf("%d\n",count);
        }
        if(line) free(line);
}

谢谢!感谢您的无聊,以便我可以学习一些东西。哦,等等,您需要退货吗?
蒂姆(Tim)

*耸肩*,如果要完全正确,还需要添加一些#include,但是编译器上的默认警告似乎并不在乎。
user606723 2011年

您可以省略,free(line)因为退出程序会隐式释放所有分配的内存-然后有一个return 0;...;)的位置。即使在示例中,将返回码保持未定义也不是好方法。顺便说一句,getline是GNU扩展-如果有人想知道的话。
maxschlepzig

@maxschlepzig:内存是由getline()分配的行所指向的吗?它是通过malloc在堆上动态分配还是在堆栈上静态分配?您说释放它不是必需的,所以它不是动态分配的吗?
蒂姆(Tim)

1
@Tim,是的,例如,如果您重构代码以使其成为一个独立的函数say-y,它会f从其他代码中多次调用,那么您必须在该函数末尾free的最后一次调用之后调用。getlinef
maxschlepzig 2011年

6

对于字符串,最简单的是和trwc(不需要用awk或过度杀伤sed)-但请注意上述有关的注释是tr,计数字节,而不是字符-

echo $x | tr -d -c '"' | wc -m

其中$x的变量包含要评估的字符串(不是文件)。


4

这是另一个只需要STD C和更少内存的C解决方案:

#include <stdio.h>

int main(int argc, char **argv)
{
  if (argc < 2 || !*argv[1]) {
    puts("Argument missing.");
    return 1;
  }
  char c = *argv[1], x = 0;
  size_t count = 0;
  while ((x = getc(stdin)) != EOF)
    if (x == '\n') {
      printf("%zd\n", count);
      count = 0;
    } else if (x == c)
      ++count;
  return 0;
}

这会不会在最后一行汇报,如果它没有尾随“\ n”
Peter.O

1
@fred,是的,这是有目的的,因为没有尾随的行\n不是真实行。这与我的其他sed / awk(tr / awk)答案相同。
maxschlepzig

3

我们可以使用grepwith regex使其更简单和强大。

计算特定字符。

$ grep -o '"' file.txt|wc -l

计算包括空格字符在内的特殊字符。

$ grep -Po '[\W_]' file.txt|wc -l

在这里,我们选择带有[\S\s]和的任何字符,并带有-o选项grep以在单独的行中打印每个匹配项(即每个字符)。然后wc -l用来计数每一行。


OP不想打印文件中所有字符的数量!他想计算/打印特定字符的数量。例如"每行有多少个;以及其他任何字符。看到他的问题,也接受了答案。
αғsнιη


2

这是一个简单的Python脚本,用于查找"文件每一行的计数:

#!/usr/bin/env python2
with open('file.txt') as f:
    for line in f:
        print line.count('"')

在这里,我们使用了count内置str类型的方法。


2

对于纯bash解决方案(但是,它是bash特定的):If $x是包含您的字符串的变量:

x2="${x//[^\"]/}"
echo ${#x2}

${x//事情,除了删除所有字符"${#x2}计算该休息的长度。

(使用expr其中的原始建议存在问题,请参阅评论:)

expr length "${x//[^\"]/}"

请注意,它特定于GNU expr并计算字节,而不是字符。与其他人exprexpr "x${x...}" : "x.*" - 1
斯特凡·Chazelas,2014年

哦,对了,谢谢!我已经用另一个想法修改了它,它的优点是根本不用外部程序。
玛丽安

2

替换a为要计算的字符。输出是每行的计数器。

perl -nE 'say y!a!!'

2

提出的解决方案的时间比较(不是答案)

答案的效率并不重要。但是,按照@josephwb方法,我尝试安排所有给出的答案的时间。

我将Victor Hugo的《悲惨世界》(伟大的书!)的葡萄牙语翻译作为输入,并计算“ a”的出现。我的版有5卷,很多页...

$ wc miseraveis.txt 
29331  304166 1852674 miseraveis.txt 

C答案是使用gcc编译的(无优化)。

每个答案运行3次,然后选择最佳答案。

不要太相信这些数字(我的机器正在执行其他任务,等等)。我与您分享这些时间,因为我得到了一些意想不到的结果,并且我相信您还会发现更多...

  • 16个定时解决方案中的14个花费不到1秒的时间;比0.1s少9个,其中许多使用管道
  • 2个解决方案,逐行使用bash,通过创建新流程处理了30k行,并在10s / 20s内计算出正确的解决方案。
  • grep -oP a树的速度快于grep -o a (10; 11 vs 12)
  • C和其他语言之间的差异并不像我预期的那么大。(7; 8 vs 2; 3)
  • (欢迎结论)

(结果以随机顺序)

=========================1 maxschlepzig
$ time sed 's/[^a]//g' mis.txt | awk '{print length}' > a2
real    0m0.704s ; user 0m0.716s
=========================2 maxschlepzig
$ time tr -d -c 'a\n' < mis.txt | awk '{ print length; }' > a12
real    0m0.022s ; user 0m0.028s
=========================3 jjoao
$ time perl -nE 'say y!a!!' mis.txt  > a1
real    0m0.032s ; user 0m0.028s
=========================4 Stéphane Gimenez
$ function countchar(){while read -r i; do echo "$i"|tr -dc "$1"|wc -c; done }

$ time countchar "a"  < mis.txt > a3
real    0m27.990s ; user    0m3.132s
=========================5 Loki Astari
$ time awk -Fa '{print NF-1}' mis.txt > a4
real    0m0.064s ; user 0m0.060s
Error : several -1
=========================6 enzotib
$ time awk '{ gsub("[^a]", ""); print length }' mis.txt > a5
real    0m0.781s ; user 0m0.780s
=========================7 user606723
#include <stdio.h> #include <string.h> // int main(int argc, char *argv[]) ...  if(line) free(line); }

$ time a.out a < mis.txt > a6
real    0m0.024s ; user 0m0.020s
=========================8 maxschlepzig
#include <stdio.h> // int main(int argc, char **argv){if (argc < 2 || !*argv[1]) { ...  return 0; }

$ time a.out a < mis.txt > a7
real    0m0.028s ; user 0m0.024s
=========================9 Stéphane Chazelas
$ time awk '{print gsub(/a/, "")}'< mis.txt > a8
real    0m0.053s ; user 0m0.048s
=========================10 josephwb count total
$ time grep -o a < mis.txt | wc -w > a9
real    0m0.131s ; user 0m0.148s
=========================11 Kannan Mohan count total
$ time grep -o 'a' mis.txt | wc -l > a15
real    0m0.128s ; user 0m0.124s
=========================12 Kannan Mohan count total
$ time grep -oP 'a' mis.txt | wc -l > a16
real    0m0.047s ; user 0m0.044s
=========================13 josephwb Count total
$ time perl -ne '$x+=s/a//g; END {print "$x\n"}'< mis.txt > a10
real    0m0.051s ; user 0m0.048s
=========================14 heemayl
#!/usr/bin/env python2 // with open('mis.txt') as f: for line in f: print line.count('"')

$ time pyt > a11
real    0m0.052s ; user 0m0.052s
=========================15 enzotib
$ time  while IFS= read -r line; do   line="${line//[!a]/}"; echo "${#line}"; done < mis.txt  > a13
real    0m9.254s ; user 0m8.724s
=========================16 bleurp
$ time awk ' {print (split($0,a,"a")-1) }' mis.txt > a14
real    0m0.148s ; user 0m0.144s
Error several -1

1
grep -n -o \" file | sort -n | uniq -c | cut -d : -f 1

grep可以完成所有繁重的工作:报告在每个行号找到的每个字符。剩下的只是对每行的计数求和,并格式化输出。

删除-n并获取整个文件的计数。

在0.015秒内计算1.5Meg文本文件的速度似乎很快。
并可以处理字符(不是字节)。


1

bash的解决方案。没有调用任何外部程序(对于短字符串更快)。

如果值在变量中:

$ a='"Hello!"'

这将打印其中"包含的数量:

$ b="${a//[^\"]}"; echo "${#b}"
2
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.