파일의 끝에서 처음으로 Grep


38

약 30.000.000 줄 (Radius Accounting)이있는 파일이 있으며 주어진 패턴의 마지막 일치 항목을 찾아야합니다.

명령 :

tac accounting.log | grep $pattern

내가 필요한 것을 제공하지만 OS가 먼저 전체 파일을 읽은 다음 파이프로 보내야하기 때문에 너무 느립니다.

따라서 마지막 줄에서 첫 번째 줄까지 파일을 읽을 수있는 빠른 것이 필요합니다.

답변:


44

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

1
주어진 패턴의 마지막 일치 항목을 찾아야하므로 tac을 사용하고 있습니다. "grep -m1"제안을 사용하면 실행 시간이 0m0.597s에서 0m0.007s \ o /로 이동합니다. 모두 고마워요!
Hábner Costa

1
@ HábnerCosta 당신은 매우 환영합니다. 왜 당신 이을 사용하는지 이해합니다 tac. 제 요점은 -m파일을 여전히 두 개의 프로그램으로 완전히 읽어야 하기 때문에 사용하지 않으면 도움이되지 않는다는 것 입니다. 그렇지 않으면, 당신은 모든 사건을 검색하고 내가 한 것처럼 마지막 사건 만 유지할 수 있습니다 tail -n 1.
terdon

6
왜 "tac [...]이 (가) 전체 파일을 처리해야합니다"라고 말합니까? tac이하는 첫 번째 일은 파일의 끝을 찾고 끝에서 블록을 읽는 것입니다. strace (1)로이를 직접 확인할 수 있습니다. 와 결합하면 grep -m매우 효율적이어야합니다.
camh

1
@camh와 결합 grep -m하면됩니다. OP는 사용하지 않았 -m으므로 grep과 tac는 모든 것을 처리했습니다.
terdon

awk선의 의미를 넓히 시겠습니까?
Sopalajo de Arrierez

12

왜 그런지

tac file | grep foo | head -n 1

첫 번째 경기에서 멈추지 않는 것은 버퍼링 때문입니다.

일반적으로 head -n 1줄을 읽은 후 종료합니다. 따라서 grepSIGPIPE를 가져 와서 두 번째 줄을 쓰는 즉시 종료해야합니다.

그러나 결과는 출력이 터미널로 가지 않기 때문에 grep버퍼링한다는 것입니다. 즉, 충분히 축적 될 때까지 작성하지 않습니다 (GNU grep을 사용한 테스트에서 4096 바이트).

grep, 8192 바이트의 데이터를 쓰기 전에는 종료되지 않으므로 아마도 몇 줄이 될 것입니다.

GNU grep를 사용 --line-buffered하면 터미널에 갔는지 여부에 관계없이 줄을 찾 자마자 작성하도록 지시하는을 사용하여 더 빨리 종료 할 수 있습니다 . 그래서 grep다음 발견 한 두 번째 줄에 종료 것입니다.

그러나 grep어쨌든 GNU 를 사용 -m 1하면 @terdon이 보여준 것처럼 대신 사용할 수 있습니다 . 첫 번째 경기에서 나올 때 더 좋습니다.

your grep가 GNU가 아닌 경우 또는 대신 grep사용할 수 있습니다 . 그러나 는 GNU 명령되고, 난 당신이있는 시스템 찾을 수 있습니다 의심 곳 GNU되지 않습니다 .sedawktactacgrepgrep

tac file | sed "/$pattern/!d;q"                             # BRE
tac file | P=$pattern awk '$0 ~ ENVIRON["P"] {print; exit}' # ERE

일부 시스템은 tail -rGNU와 동일한 작업을 수행해야합니다 tac.

일반 (찾을 수있는) 파일의 경우 파일을 뒤로 읽기 때문에 효율적 tac이며 tail -r파일을 뒤로 인쇄하기 전에 메모리에서 파일을 완전히 읽는 것이 아닙니다 ( @slm의 sed 방식 또는 tac비정규 파일의 경우와 같이) .

사용 가능 하지 tac않거나 tail -r사용할 수 없는 시스템에서 유일한 옵션은 다음과 같은 프로그래밍 언어를 perl사용 하여 수동으로 역독을 구현하는 것입니다.

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

또는:

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

그러나 이는 모든 일치 항목을 찾고 마지막 일치 항목 만 인쇄하는 것을 의미합니다.


4

다음은 처음부터 패턴이 처음 발생한 위치를 찾는 가능한 솔루션입니다.

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

단, 선의 시작과 패턴 사이에있는 모든 것을 잃게됩니다.
ychaouche

2

sed 사용

다음을 사용하여 @Terdon의 훌륭한 답변에 대한 대체 방법을 보여줍니다sed .

$ 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

1
그것은 (특히 sed하나) grep 5 | tail -n1또는 보다 몇 배나 느릴 것 sed '/5/h;$!d;g'입니다. 또한 잠재적으로 많은 메모리를 사용합니다. 아직 GNU를 사용하고 있기 때문에 이식성이 떨어 grep -m집니다.
Stéphane Chazelas
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.