約30.000.000行(Radius Accounting)のファイルがあり、指定されたパターンの最後の一致を見つける必要があります。
コマンド:
tac accounting.log | grep $pattern
私が必要なものを提供しますが、OSは最初にファイル全体を読み取ってからパイプに送信する必要があるため、遅すぎます。
したがって、ファイルを最後の行から最初の行まで読み取ることができる高速なものが必要です。
tac
は、grep -m 1
(GNU grep
と仮定)も使用して、最初の一致の後にgrep
を停止する場合にのみ役立ちます。
tac accounting.log | grep -m 1 foo
man grep
から:
-m NUM, --max-count=NUM
Stop reading a file after NUM matching lines.
質問の例では、tac
とgrep
の両方がファイル全体を処理する必要があるため、tac
を使用しても意味がありません。
したがって、grep -m
を使用しない限り、tac
をまったく使用せず、grep
の出力を解析して、最後の一致を取得します。
grep foo accounting.log | tail -n 1
別のアプローチは、Perlまたはその他のスクリプト言語を使用することです。たとえば(where $pattern=foo
):
Perl -ne '$l=$_ if /foo/; END{print $l}' file
または
awk '/foo/{k=$0}END{print k}' file
理由
tac file | grep foo | head -n 1
最初の一致で停止しないのはバッファリングのためです。
通常、head -n 1
は行を読み取った後に終了します。したがって、grep
は、2行目を書き込むとすぐにSIGPIPEを取得して終了します。
しかし、何が起こるかというと、その出力は端末に送信されないため、grep
がバッファリングします。つまり、十分に蓄積されるまでは書き込みを行いません(GNU grepを使用したテストでは4096バイト)。
つまり、grep
は8192バイトのデータを書き込む前に終了しないため、おそらく数行になります。
GNU grep
を使用する場合は、--line-buffered
を使用して、端末に行くかどうかに関係なく、行が見つかったらすぐに書き込むように指示することで、より早く終了させることができます。したがって、grep
は、2行目で終了します。
ただし、GNU grep
を使用すると、@ terdonが示したように、代わりに-m 1
を使用できます。これは、最初の一致で終了するため、より優れています。
grep
がGNU grep
でない場合は、代わりにsed
またはawk
を使用できます。しかし、tac
はGNUコマンドであるため、tac
がGNU grep
ではないgrep
のシステムが見つかることはありません。
tac file | sed "/$pattern/!d;q" # BRE
tac file | P=$pattern awk '$0 ~ ENVIRON["P"] {print; exit}' # ERE
一部のシステムでは、GNUと同じことを行うためにtail -r
を使用しています__ tac
と同じです。
通常の(シーク可能な)ファイルの場合、tac
とtail -r
はファイルを逆方向に読み取るため効率的であり、逆方向に出力する前にメモリ内のファイルを完全に読み取るだけではないことに注意してください( @ slm's sedアプローチ または通常でないファイルではtac
)。
tac
もtail -r
も使用できないシステムでは、Perl
のようなプログラミング言語を使用して手動で逆方向読み取りを実装するか、次のように使用することが唯一のオプションです。
grep -e "$pattern" file | tail -n1
または:
sed "/$pattern/h;$!d;g" file
しかし、それらはすべての一致を見つけ、最後のものだけを出力することを意味します。
最後からパターンが最初に出現する場所を見つける可能な解決策は次のとおりです。
tac -s "$pattern" -r accounting.log | head -n 1
これは-s
および-r
tac
のスイッチは次のとおりです。
-s, --separator=STRING
use STRING as the separator instead of newline
-r, --regex
interpret the separator as a regular expression
sed
を使用して @ Terdonの細かい答え の代替方法をいくつか示します。
$ sed '1!G;h;$!d' file | grep -m 1 $pattern
$ sed -n '1!G;h;$p' file | grep -m 1 $pattern
$ seq 10 > file
$ sed '1!G;h;$!d' file | grep -m 1 5
5
$ sed -n '1!G;h;$p' file | grep -m 1 5
5
おまけとして、ここではPerlで覚えやすい表記を少し示します。
$ Perl -e 'print reverse <>' file | grep -m 1 $pattern
$ Perl -e 'print reverse <>' file | grep -m 1 5
5