【问题标题】:grep variables and give informative ouputgrep 变量并提供信息丰富的输出
【发布时间】:2013-01-10 06:11:08
【问题描述】:

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

我的虚拟示例如下所示:

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 个字母,所以我无法创建像这个单词一样命名的文件。

【问题讨论】:

    标签: perl bash sed awk grep


    【解决方案1】:

    您可以使用grepoption -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 替换为您的最大字长和其他数字。

    【讨论】:

    • 还有一些我得到的单词“列:行太长”
    • 是的,它区分大小写。使用 grep 的 -i 选项使其不区分大小写。列错误似乎是列错误。来自BUGS 部分的手册页:Input lines are limited to LINE_MAX (2048) bytes in length.。如果您知道最大字符数,您也可以使用printf 进行输出。现在更新我的答案。
    • 我发现这个grep -i 选项真的很慢,虽然不知道为什么。所以我在grep 之前只是tr '[a-z]' '[A-Z]' All_files,这样工作起来要快得多。
    【解决方案2】:

    作为 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
    

    【讨论】:

      【解决方案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 脚本:每个文件只读取一次。

      【讨论】:

        【解决方案4】:

        一个 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
        

        【讨论】:

          【解决方案5】:
          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
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-02-09
            • 1970-01-01
            • 2011-10-26
            • 2016-10-05
            • 1970-01-01
            • 2022-10-31
            • 2017-02-01
            • 2017-08-07
            相关资源
            最近更新 更多