grep 使用文件中的许多模式并显示哪个模式与哪个文件匹配而无需重新读取文件

grep 使用文件中的许多模式并显示哪个模式与哪个文件匹配而无需重新读取文件

我对 m 个文件有 n 个单独的非固定 grep。我只需要知道每个文件中是否至少有 1 个匹配项,但每个模式都需要这个。我目前制作了 n 个单独的 grep,这样我可以稍后将它们全部合并,但是它非常慢并且有些文件很大。

有没有一种方法可以替换这些文件,不需要我读取所有文件 n 次(不需要是单独的文件,只要我可以将模式(不匹配)与包含匹配项的文件进行匹配)。 grep -f 看起来很有希望,但它显示与任何模式匹配的文件,而不是与每个模式匹配的文件。

ex 稍后合并到 1 个大文件中的内容:

grep -liE  pattern1  file_glob* > temp_pattern1.txt && sed s/^/escapedpattern1 / temp_pattern1.txt
grep -liE  pattern2   file_glob* > temp_pattern2.txt && sed s/^/escapedpattern2 / temp_pattern2.txt
...
grep -liE  patternN   file_glob* > temp_patternN.txt && sed s/^/escapedpatternN / temp_patternN.txt

temp_pattern1.txt
pattern1 /path/to/file1
pattern1 /path/to/file2
pattern1 /path/to/file3

temp_pattern2.txt
pattern2 /path/to/file1
pattern2 /path/to/file3
...
temp_patternN.txt
pattern N /path/to/fileM

答案1

如果您想使用grep,您能做的最好的事情就是使用该-m 1选项grep在第一个匹配时停止读取其当前输入文件。您仍将多次读取每个输入文件(每个模式一次),但它应该更快(除非匹配位于文件的最后一行或附近)。

例如

#!/bin/bash

# Speed up each grep by exiting on 1st match with -m 1
#
# This still reads each file multiple times, but should run faster because it
# won't read the entire file each time unless the match is on the last line.
#
# Also reduce repetitive code by using an array and a for loop iterating over
# the indices of the array, rather than the values

patterns=(pattern1 pattern2 pattern3 patternN)

# iterate over the indices of the array (with `${!`), not the values.
for p in "${!patterns[@]}"; do
  # escape forward- and back- slashes in pattern
  esc=$(echo "${patterns[$p]}" | sed -e 's:/:\\/:g; s:\\:\\\\:g')
  grep -liE -m 1 "${patterns[$p]}" file_glob* |
    sed -e "s/^/$esc\t/" > "temp_pattern$(($p+1)).txt"
done

注意:之所以$p+1存在,是因为 bash 数组从零开始。 +1 使 temp_patterns 文件从 1 开始。


awk如果您使用像或 这样的脚本语言,就可以做您想做的事perl。例如,以下 perl 脚本仅读取每个输入文件一次,并根据该文件中尚未出现的每个模式检查每一行。它跟踪已在特定文件中看到的模式(使用数组@seen),并注意到何时在文件中看到所有可用模式(也使用@seen)并在这种情况下关闭当前文件。

#!/usr/bin/perl
use strict;

# array to hold the patterns
my @patterns = qw(pattern1 pattern2 pattern3 patternN);

# Array-of-Arrays (AoA, see man pages for perllol and perldsc)
# to hold matches
my @matches;

# Array for keeping track of whether current pattern has
# been seen already in current file
my @seen;

# read each line of each file
while(<>) {
  # check each line against all patterns that haven't been seen yet
  for my $i (keys @patterns) {
    next if $seen[$i];
    if (m/$patterns[$i]/i) {
      # add the pattern and the filename to the @matches AoA
      push @{ $matches[$i] }, "$patterns[$i]\t$ARGV";
      $seen[$i] = 1;
    }
  };

  # handle end-of-file AND having seen all patterns in a file
  if (eof || $#seen == $#patterns) {
    #print "closing $ARGV on line $.\n" unless eof;
    # close the current input file.  This will have
    # the effect of skipping to the next file.
    close(ARGV);
    # reset seen array at the end of every input file
    @seen = ();
  };
}

# now create output files
for my $i (keys @patterns) {
  #next unless @{ $matches[$i] }; # skip patterns with no matches
  my $outfile = "temp_pattern" . ($i+1) . ".txt";
  open(my $out,">",$outfile) || die "Couldn't open output file '$outfile' for write: $!\n";
  print $out join("\n", @{ $matches[$i] }), "\n";
  close($out);
}

if (eof || $#seen == $#patterns)行测试当前文件上的 eof(文件结尾)或者如果我们已经看到当前文件中的所有可用模式(即@seen 中的元素数量等于@patterns 中的元素数量)。

在这两种情况下,我们都希望将 @seen 数组重置为空,以便为下一个输入文件做好准备。

在后一种情况下,我们还希望尽早关闭当前输入文件 - 我们已经看到了我们想要在其中看到的所有内容,无需继续读取和处理文件的其余部分。

顺便说一句,如果您不想创建空文件(即当模式不匹配时),请取消注释next unless @{ $matches[$i] }for 循环输出中的行。


如果您不需要或不需要临时文件,并且只想将所有匹配项输出到一个文件,请将 for 循环的最终输出替换为:

for my $i (keys @patterns) {
  #next unless @{ $matches[$i] }; # skip patterns with no matches
  print join("\n", @{ $matches[$i] }), "\n";
}

并将输出重定向到文件。


顺便说一句,如果您想添加模式在文件中首次出现的行号,请更改:

push @{ $matches[$i] }, "$patterns[$i]\t$ARGV";

push @{ $matches[$i] }, "$patterns[$i]\t$.\t$ARGV";

$.是一个内置的 perl 变量,它保存输入的当前行号<>。每当当前文件 ( ARGV) 关闭时,它都会重置为零。

相关内容