3

我想看看文件/行中提到了多少次特定的单词。

我的虚拟示例如下所示:

cat words
blue
red 
green
yellow 

cat text
TEXTTEXTblueTEXTTEXTblue
TEXTTEXTgreenblueTEXTTEXT
TEXTTEXyeowTTEXTTEXTTEXT

我正在这样做:

for i in $(cat words); do grep "$i" text | wc >> output; done

cat output
  2       2      51
  0       0       0
  1       1      26
  0       0       0

但我真正想得到的是:
1.用作变量的单词;
2. 在多少行(除了文本命中)中找到了单词。

首选输出如下所示:

blue    3   2
red     0   0 
green   1   1
yellow  0   0

$1 - 被 grep'ed 的变量
$2 - 在文本中找到变量的次数
$3 - 在多少行中找到变量

希望有人可以帮助我使用 grep、awk、sed 来完成这项工作,因为它们对于大型数据集来说足够快,但是 Perl 单行也可以帮助我。

编辑

试过这个

   for i in $(cat words); do grep "$i" text > out_${i}; done && wc out*  

它看起来不错,但是有些单词超过 300 个字母,所以我无法创建像单词一样命名的文件。

4

5 回答 5

4

您可以使用仅打印匹配行的匹配部分的grep 选项-o每个匹配项在单独的输出行上

while IFS= read -r line; do
    wordcount=$(grep -o "$line" text | wc -l)
    linecount=$(grep -c "$line" text)
    echo $line $wordcount $linecount
done < words | column -t

您可以将它们全部放在一条线中,使其成为一条线。

如果 column 给出“column too long”错误,您可以使用 printf ,前提是您知道最大字符数。使用以下代替echo并删除管道到列:

printf "%-20s %-2s %-2s\n" "$line" $wordcount $linecount

如果需要,将 20 替换为您的最大字长和其他数字。

于 2013-01-26T10:51:44.073 回答
3

这是一个类似的 Perl 解决方案;而是写成一个完整的脚本。

#!/usr/bin/perl

use 5.012;

die "USAGE: $0 wordlist.txt [text-to-search.txt]\n" unless @ARGV;

my $wordsfile = shift @ARGV;
my @wordlist = do {
    open my $words_fh, "<", $wordsfile or die "Can't open $wordsfile: $!";
    map {chomp; length() ? $_ : ()} <$words_fh>;
};

my %words;
while (<>) {
    for my $word (@wordlist) {
        my $cnt = 0;
        $cnt++ for /\Q$word\E/g;
        $words{$word}[0] += $cnt;
        $words{$word}[1] += 1&!! $cnt; # trick to force 1 or 0.
    }
}

# sorts output after frequency. remove `sort {...}` to get unsorted output.
for my $key (sort {$words{$b}->[0] <=> $words{$a}->[0] or $a cmp $b} keys %words) {
    say join "\t", $key, @{ $words{$key} };
}

示例输出:

blue    3       2
green   1       1
red     0       0
yellow  0       0

优于 bash 脚本的优势:每个文件只读取一次。

于 2013-01-26T13:16:41.493 回答
1

作为 Perl 单行,这变得非常难看(部分原因是它需要从两个文件中获取数据,并且只能在标准输入上发送一个,部分原因是需要计算匹配的行数和匹配的总数) ,但是你去:

perl -E 'undef $|; open $w, "<", "words"; @w=<$w>; chomp @w; $r{$_}=[0,{}] for @w; my $re = join "|", @w; while(<>) { $l++; while (/($re)/g) { $r{$1}[0]++; $r{$1}[1]{$l}++; } }; say "$_\t$r{$_}[0]\t" . scalar keys %{$r{$_}[1]} for @w' < text

这需要 perl 5.10 或更高版本,但将其更改为支持 5.8 及更早版本是微不足道的。(将 更改-E-e,更改say为,并在每行输出的末尾print添加一个。)\n

输出:

blue    3   2
red     0   0
green   1   1
yellow  0   0
于 2013-01-26T12:54:39.537 回答
1

一个 awk(gawk) oneliner 可以让你免于 grep 难题:

  awk 'NR==FNR{n[$0];l[$0];next;}{for(w in n){ s=$0;t=gsub(w,"#",s); n[w]+=t;l[w]+=t>0?1:0;}}END{for(x in n)print x,n[x],l[x]}' words text

稍微格式化一下代码:

awk 'NR==FNR{n[$0];l[$0];next;}
    {for(w in n){ s=$0;
        t=gsub(w,"#",s); 
        n[w]+=t;l[w]+=t>0?1:0;}
    }END{for(x in n)print x,n[x],l[x]}' words text

用你的例子测试:

kent$  awk 'NR==FNR{n[$0];l[$0];next;}{for(w in n){ s=$0;t=gsub(w,"#",s); n[w]+=t;l[w]+=t>0?1:0;}}END{for(x in n)print x,n[x],l[x]}' words text
yellow  0 0
red  0 0
green 1 1
blue 3 2

如果要格式化输出,可以将 awk 输出通过管道传输到column -t

所以它看起来像:

yellow  0  0
red     0  0
green   1  1
blue    3  2
于 2013-01-26T19:56:46.633 回答
1
awk '
NR==FNR { words[$0]; next }
{
   for (word in words) {
      count = gsub(word,word)
      if (count) {
         counts[word] += count
         lines[word]++
      }
   }
}
END { for (word in words) printf "%s %d %d\n", word, counts[word], lines[word] }
' file
于 2013-01-27T12:37:31.517 回答