Найти последнее вхождение строки в нескольких файлах

9

Мне нужно выполнить поиск по нескольким файлам журнала (все файлы, созданные за последние 24 часа, все они находятся в одном каталоге), чтобы найти последнее вхождение строки. Это команда, которую я написал:

find . -mtime 1 | grep fileprefix | xargs grep 'search string' | tail -1

Но это возвращает только последнюю строку для одного файла. Любые предложения о том, как настроить это, чтобы получить все строки?

Lokesh
источник
ты пытался инвертировать хвост и последний grep? найти . -mtime 1 | grep fileprefix | Ксаргс хвост -1 | grep 'search string'
Матье,

Ответы:

4

Принимая во внимание возможности GNU:

find . -mtime -1 -exec bash -c \
'for f; do tac "$f" | grep -m1 fileprefix; done' _ {} +
Iruvar
источник
Не могли бы вы уточнить назначение 'bash -c \', поскольку я уже использую оболочку bash. Также цель '_ {} +' в конце.
Lokesh
@Lokesh, вы можете findвыполнять команды для файлов, используя -exec. С помощью bash -cмы создаем bashоболочку, которая просматривает файлы, найденные findи выполняемые tac .. | grep -m1 fileprefixна каждом из них
iruvar
Я пытался расширить строковую фильтрацию цикла for, добавив команду cut, т.е. для f; сделать так "$ f" | grep -m1 fileprefix | cut -d '' -f4,7-8, но когда я вставляю команду cut, она выдает ошибку неожиданного конца файла Подскажите, пожалуйста, что я делаю не так.
Lokesh
@lokesh, используйте -d" "с разрезом. Двойные кавычки вместо одинарных
iruvar
1
Команда findможет фильтровать по префиксу файла; для этого grepне нужно Также удивительно, что строка поиска не фигурирует в этом ответе.
Джонатан Леффлер
8

Если все в одном каталоге, вы можете сделать:

for file in *fileprefix*; do
    grep 'search string' "$file" | tail -1
done

Если это большие файлы, возможно, стоит ускорить процесс, используя tacдля печати файл в обратном порядке (сначала последнюю строку), а затем grep -m1для сопоставления с первым вхождением. Таким образом, вам не нужно читать весь файл:

for file in *fileprefix*; do
    tac file | grep -m1 'search string'
done

Оба из них предполагают, что нет подходящих каталогов fileprefix. Если есть, вы получите ошибку, которую можете просто проигнорировать. Если это проблема, проверьте только файлы:

 for file in *fileprefix*; do
    [ -f "$file" ] && tac file | grep -m1 'search string'
 done

Если вам также нужно напечатать имя файла, добавляйте -Hк каждому grepвызову. Или, если ваш grepне поддерживает его, скажите, чтобы он также просматривал /dev/null. Это не изменит вывод, но, поскольку grepему дается несколько файлов, он всегда печатает имя файла для каждого попадания:

for file in *fileprefix*; do
    grep 'search string' "$file" /dev/null | tail -1
done
Тердон
источник
«Таким образом, вам не нужно читать весь файл» - а? Нет, вы избегаете чтения всего файла в grep, но вместо этого вы помещаете весь файл через tac. Мне не ясно, что это будет быстрее, хотя это будет зависеть от того, было ли совпадение рядом с началом или концом файла.
Жиль "ТАК - перестань быть злым"
@ Жиль, нет, ты не пропустил весь файл tac. Он выйдет, как только будет найдено первое совпадение. Я только что протестировал текстовый файл 832M и шаблон, найденный в последней строке. grep -m 1 pattern fileинструмент ~ 7 секунд и tac file | grep -m1 patternвзял 0.009.
Terdon
4
find . ! -name . -prune -mtime 1 -name 'fileprefix*' \
     -exec sed -se'/searchstring/h;$!d;x' {} +

... будет работать, если у вас есть GNU, sedкоторый поддерживает -sопцию eparate files и POSIX find.

Вы, вероятно, должны добавить квалификаторы ! -type dили -type f, хотя, потому что попытка чтения каталога не будет очень полезной, и дальнейшее сужение диапазона до обычных файлов может избежать зависания чтения в канальном или последовательном файле устройства.

Логика невероятно проста - sedперезаписывает свое hстарое пространство копией любой входной строки, которая совпадает searchstring, а затем dотключает все выходные строки, кроме последней, для каждого входного файла. Когда он добирается до последней строки, он xменяет свое пространство удержания и шаблонные пространства, и поэтому, если он вообще searchstringбыл найден во время чтения файла, последнее такое вхождение будет автоматически напечатано для вывода, в противном случае он записывает пустую строку. (добавьте /./!dв конец sedскрипта, если это нежелательно) .

Это будет делать один sedвызов для некоторых входных файлов 65k - или независимо от вашего ARG_MAXпредела. Это должно быть очень эффективным решением, и оно довольно просто реализуется.

Если вам также нужны имена файлов, учитывая недавний GNU, sedвы можете записать их в отдельные строки с помощью Fкоманды, или вы можете распечатать их findв отдельном списке для пакета, добавив -printосновной после +.

mikeserv
источник
1

Как насчет:

find . -mtime -1 -name "fileprefix*" -exec sh -c \
'echo "$(grep 'search string' $1 | tail -n 1),$1"' _ {} \;

Вышеприведенное дает хороший вывод с последним вхождением строки поиска в каждом файле, за которым следует соответствующее имя файла после запятой (измените часть ", $ 1" в echo, чтобы изменить форматирование, или удалите его, если в этом нет необходимости). Пример вывода, который ищет строку поиска «10» в файлах с префиксом «file», выглядит следующим образом:

[dmitry@localhost sourceDir]$ find . -mtime -1 -name "file*" -exec  sh -c 'echo "$(grep '10' $1 | tail -n 1),$1"' _ {} \;
Another data 02 10,./file02.log
Some data 01 10,./file01.log
Yet another data 03 10,./file03.log 
Дмитрий Алекс
источник
1
find . -mtime 1 -name 'fileprefix*' -exec grep -Hn 'search string' {} + |
    sort -t: -k1,2 -n | 
    awk -F: '{key=$1 ; $1="" ; $2="" ; gsub(/^  /,"",$0); a[key]=$0} 
             END {for (key in a) { print key ":" a[key] }}'

Это использует GNU grep«s -Hи -nопции всегда печатать как имя файла и LINENUMBER всех матчей, то он сортирует по имени файла и LINENUMBER, и трубы ее в AWK, который хранит последний матч для каждого файла в массив, и в конце концов печать Это.

Довольно грубый метод, но он работает.

саз
источник