Перехоплення від кінця файлу до початку


38

У мене є файл з приблизно 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 [...] повинен обробити весь файл"? Перше, що робить tac - це прагнути до кінця файлу і прочитати блок з кінця. Ви можете перевірити це самостійно за допомогою штриху (1). У поєднанні з grep -mним він повинен бути досить ефективним.
camh

1
@camh у поєднанні з grep -mним є. ОП не використовував, -mтому і grep, і tac обробляли всю справу.
terdon

Чи можете ви, будь ласка, розширити значення awkрядка?
Sopalajo de Arrierez

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робити те саме, що tacробить GNU .

Зауважте, що для звичайних (доступних для пошуку) файлів tacі tail -rефективні, оскільки вони читають файли назад, вони не просто читають файл повністю в пам'яті перед тим, як надрукувати його назад (як би підходив @ slm або 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

Показано кілька альтернативних методів для точної відповіді @ Тердона, використовуючи 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.