我对 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
) 关闭时,它都会重置为零。