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
それでも各入力ファイルは何度も読み取られますが(パターンごとに1回)、より高速です(一致項目がファイルの最後の行にあるかその近くにない限り)。
例えば
#!/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
0にリセットされます。