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

Это - старый вопрос, но любопытный, другие могут рассмотреть команду newusers. Это присутствует и в системе RHEL5.5 и в системе Ubuntu 12.04, которую я использую, таким образом, я взял бы предположение, это будет доступно в репозиториях для большинства дистрибутивов.

От man newusers:

Команда newusers читает файл пар имени пользователя и пароля в виде открытого текста и использует эту информацию, чтобы обновить группу существующих пользователей или создать новых пользователей. Каждая строка находится в том же формате как стандартный файл паролей (см. passwd (5)) за исключениями, объясненными ниже

41
28.02.2016, 22:25
4 ответа

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
45
27.01.2020, 19:35
  • 1
    я использую tac, потому что я должен найти последнее соответствие данного шаблона. Используя Ваше предложение "grep-m1" время выполнения проходит от 0m0.597s до 0m0.007s \o/. Спасибо все! –  Hábner Costa 02.02.2014, 19:46
  • 2
    @HábnerCosta пожалуйста. Я понимаю, почему Вы используете tac, моя точка была то, что это не помогает, если Вы также не используете -m так как файл все еще должен быть считан полностью двумя программами. Иначе Вы могли просто искать все происшествия и сохранить только последнее, как я делаю с tail -n 1. большое спасибо –  terdon♦ 02.02.2014, 19:49
  • 3
    , Почему Вы говорите "tac [...], должен обработать весь файл"? Первая вещь tac делает, ищут в конец файла и читают блок из конца. Можно проверить это сами с strace (1). В сочетании с grep -m, это должно быть довольно эффективно. –  camh 04.02.2014, 10:16
  • 4
    @camh в сочетании с grep -m это. OP не использовал -m так и grep и tac обрабатывали все это. –  terdon♦ 04.02.2014, 15:57

Причина, почему

tac file | grep foo | head -n 1

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

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

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

То, что это означает, является этим grep не выйдет, прежде чем это записало 8 192 байта данных, так вероятно, довольно много строк.

С 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 делает.

Обратите внимание что для регулярных (seekable) файлов, tac и tail -r эффективны, потому что они действительно читают файлы назад, они только читают файл полностью в памяти прежде, чем распечатать его назад (как подход sed @slm или tac на нерегулярных файлах был бы).

В системах, где ни один tac ни tail -r доступны, единственные опции состоят в том, чтобы реализовать чтение в обратном направлении вручную с языками программирования как perl или использование:

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

Или:

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

Но они означают находить все соответствия и только печатают последнее.

12
27.01.2020, 19:35

Вот возможное решение, которое найдет местоположение первого вхождения шаблона от в последний раз:

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
4
27.01.2020, 19:35

Используя 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
2
27.01.2020, 19:35
  • 1
    Это (особенно sed один), вероятно, чтобы быть несколькими порядками величины медленнее, чем grep 5 | tail -n1 или sed '/5/h;$!d;g'. Это будет также потенциально использовать большую память. Это не намного более портативно, поскольку Вы все еще используете GNU grep -m. –  Stéphane Chazelas 04.02.2014, 13:26

Теги

Похожие вопросы