web-dev-qa-db-ja.com

ファイルの終わりから始まりまでのGrep

約30.000.000行(Radius Accounting)のファイルがあり、指定されたパターンの最後の一致を見つける必要があります。

コマンド:

tac accounting.log | grep $pattern

私が必要なものを提供しますが、OSは最初にファイル全体を読み取ってからパイプに送信する必要があるため、遅すぎます。

したがって、ファイルを最後の行から最初の行まで読み取ることができる高速なものが必要です。

45
Hábner Costa

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.  

質問の例では、tacgrepの両方がファイル全体を処理する必要があるため、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
48
terdon

理由

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と同じです。

通常の(シーク可能な)ファイルの場合、tactail -rはファイルを逆方向​​に読み取るため効率的であり、逆方向に出力する前にメモリ内のファイルを完全に読み取るだけではないことに注意してください( @ slm's sedアプローチ または通常でないファイルではtac)。

tactail -rも使用できないシステムでは、Perlのようなプログラミング言語を使用して手動で逆方向読み取りを実装するか、次のように使用することが唯一のオプションです。

grep -e "$pattern" file | tail -n1

または:

sed "/$pattern/h;$!d;g" file

しかし、それらはすべての一致を見つけ、最後のものだけを出力することを意味します。

12

最後からパターンが最初に出現する場所を見つける可能な解決策は次のとおりです。

tac -s "$pattern" -r accounting.log | head -n 1

これは-sおよび-rtacのスイッチは次のとおりです。

-s, --separator=STRING
use STRING as the separator instead of newline

-r, --regex
interpret the separator as a regular expression
4
mkc

Sedの使用

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で覚えやすい表記を少し示します。

$ Perl -e 'print reverse <>' file | grep -m 1 $pattern

$ Perl -e 'print reverse <>' file | grep -m 1 5
5
2
slm