Grep от конца файла до начала


39

У меня есть файл с 30 000 000 строк (учет радиуса), и мне нужно найти последнее совпадение данного шаблона.

Команда:

tac accounting.log | grep $pattern

дает то, что мне нужно, но это слишком медленно, потому что ОС должна сначала прочитать весь файл, а затем отправить в канал.

Итак, мне нужно что-то быстрое, что может прочитать файл с последней строки до первой.

Ответы:


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.  

В примере , в вашем вопросе, как tacи grepнеобходимость обработать весь файл так , используя tacэто своего рода бессмысленно.

Так что, если вы не используете grep -m, вообще не используйте tac, просто проанализируйте выходные данные, grepчтобы получить последнее совпадение:

grep foo accounting.log | tail -n 1 

Другой подход заключается в использовании Perl или любого другого языка сценариев. Например (где $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 /. Спасибо всем!
Хабнер Коста

1
@ HábnerCosta, пожалуйста. Я понимаю, почему вы используете tac, моя точка зрения заключалась в том, что это не поможет, если вы также не используете, -mтак как файл все еще должен быть полностью прочитан двумя программами. В противном случае вы можете просто найти все события и оставить только последний, как я tail -n 1.
Terdon

6
Почему вы говорите, что «[...] нужно обрабатывать весь файл»? Первое, что делает tac - это поиск в конце файла и чтение блока с конца. Вы можете проверить это самостоятельно с помощью strace (1). В сочетании с grep -mэтим он должен быть достаточно эффективным.
Camh

1
@camh в сочетании с grep -mэтим есть. OP не использовал, -mпоэтому grep и tac обрабатывали все это.
Тердон

Не могли бы вы расширить смысл этой awkстроки?
Сопалахо де Арриерес

12

Причина по которой

tac file | grep foo | head -n 1

не останавливается на первом матче из-за буферизации.

Обычно head -n 1выходит после прочтения строки. Поэтому grepследует получить SIGPIPE и завершить работу, как только он напишет вторую строку.

Но что происходит, так это потому, что его вывод не идет в терминал, grepбуферизует его. То есть он не пишет его, пока не накопит достаточно (4096 байт в моем тесте с GNU grep).

Это означает, что grepон не завершится до того, как записал 8192 байта данных, так что, вероятно, довольно много строк.

С GNU grepвы можете сделать так, чтобы он выходил быстрее, используя команду, --line-bufferedкоторая говорит ему писать строки, как только они найдены, независимо от того, идет ли к терминалу или нет. Так grepчто тогда выходил бы на второй строке, которую он находит.

Но с GNU в grepлюбом случае вы можете использовать -m 1вместо этого, как показало @terdon, что лучше, когда он выходит при первом совпадении.

Если ваш grepне GNU grep, то вы можете использовать sedили awkвместо. Но, tac будучи командой GNU, я сомневаюсь, что вы найдете систему, в tacкоторой grepнет GNU grep.

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

Некоторые системы должны tail -rделать то же самое, что и GNU tac.

Обратите внимание , что для регулярных (доступных для поиска) файлов, tacи tail -rявляются эффективными , потому что они делают читать файлы назад, они не просто чтение файла полностью в памяти перед печатью назад (как @ ОДС это СЭД подхода или 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:

$ 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.
Стефан Шазелас
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.