Encontre a última ocorrência de string em vários arquivos

9

Preciso pesquisar vários arquivos de log (todos os arquivos gerados nas últimas 24 horas, todos mantidos no mesmo diretório) para encontrar a última ocorrência de uma string. Este é o comando que escrevi:

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

Mas isso retorna apenas a última linha de um arquivo. Alguma sugestão sobre como ajustar isso para obter todas as linhas?

Lokesh
fonte
você tentou inverter a cauda e o último grep? encontrar . 1 | grep fileprefix | xargs tail -1 | grep 'search string'
Mathieu

Respostas:

4

Assumindo instalações GNU:

find . -mtime -1 -exec bash -c \
'for f; do tac "$f" | grep -m1 fileprefix; done' _ {} +
iruvar
fonte
Você pode elaborar o propósito do 'bash -c \', pois eu já estou usando o shell do bash. Também o objetivo de '_ {} +' no final.
Lokesh
@Lokesh, você pode findexecutar comandos em arquivos usando -exec. Com bash -c, estamos gerando uma bashconcha que percorre os arquivos encontrados por finde executa tac .. | grep -m1 fileprefixem cada
Iruvar
Eu estava tentando estender a string filtrando o loop for incluindo o comando cut, por exemplo, para f; faça tac "$ f" | grep -m1 arquivoprefixo | cut -d '' -f4,7-8 mas no momento em que coloco o comando cut, ele me dá um erro inesperado no final do arquivo. Você pode sugerir o que estou fazendo de errado.
Lokesh
@lokesh, use -d" "com corte. Aspas duplas em vez de simples
iruvar
1
O findcomando pode filtrar o prefixo do arquivo; o grepnão deve ser necessário para isso. Também é surpreendente que a string de pesquisa não apareça nesta resposta.
Jonathan Leffler
8

Se tudo estiver em um único diretório, você poderá:

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

Se esses arquivos forem grandes, pode valer a pena acelerar usando-o tacpara imprimir o arquivo na ordem inversa (última linha primeiro) e depois grep -m1corresponder à primeira ocorrência. Dessa forma, você evita ter que ler o arquivo inteiro:

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

Ambos assumem que não há diretórios correspondentes fileprefix. Se houver, você receberá um erro que pode ser ignorado. Se isso for um problema, verifique apenas os arquivos:

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

Se você também precisar do nome do arquivo impresso, adicione -Ha cada grepchamada. Ou, se o seu grepnão suportar, peça para ele também pesquisar /dev/null. Isso não altera a saída, mas como grepsão fornecidos vários arquivos, sempre será impresso o nome do arquivo para cada ocorrência:

for file in *fileprefix*; do
    grep 'search string' "$file" /dev/null | tail -1
done
terdon
fonte
“Dessa forma, você evita ter que ler o arquivo inteiro” - uh? Não, você evita ler o arquivo inteiro no grep, mas coloca o arquivo inteiro no tac. Não está claro para mim que isso seria mais rápido, embora isso dependa se a correspondência está próxima do início ou do fim do arquivo.
Gilles 'SO- stop be evil'
@ Gilles não, você também não coloca o arquivo inteiro tac. Ele sairá assim que a primeira correspondência for encontrada. Acabei de testar com um arquivo de texto 832M e um padrão encontrado na última linha. grep -m 1 pattern fileferramenta ~ 7 segundos e tac file | grep -m1 patternlevou 0.009.
terdon
4
find . ! -name . -prune -mtime 1 -name 'fileprefix*' \
     -exec sed -se'/searchstring/h;$!d;x' {} +

... funcionará se você tiver o GNU sedque suporta a -sopção eparate files e um POSIX find.

Você provavelmente deve adicionar os ! -type dou -type fqualificadores, porque tentar ler um diretório não será muito útil, e restringir ainda mais o intervalo a arquivos regulares pode evitar uma leitura pendurada em um arquivo de pipe ou dispositivo serial.

A lógica é incrivelmente simples - sedsobrescreve seu hespaço antigo com uma cópia de qualquer linha de entrada que corresponda searchstringe delimina da saída todas as linhas de entrada, exceto a última para cada arquivo de entrada. Quando chega à última linha, ele xaltera seus espaços de espera e padrão e, portanto, se searchstringfoi encontrado durante a leitura do arquivo, a última ocorrência desse tipo será impressa automaticamente para saída, caso contrário, ele grava uma linha em branco. (adicione /./!dao final do sedscript, se isso for indesejável) .

Isso fará uma única sedchamada por alguns arquivos de entrada de 65k - ou qualquer que seja o seu ARG_MAXlimite. Essa deve ser uma solução de alto desempenho e é simplesmente implementada.

Se você também deseja os nomes de arquivos, dado um GNU recente, sedvocê pode escrevê-los em linhas separadas com o Fcomando, ou pode imprimi-los findem uma lista separada por lote, acrescentando o -printprimário depois +.

mikeserv
fonte
1

E se:

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

A descrição acima fornece uma boa saída com a última ocorrência de uma sequência de pesquisa em cada arquivo, seguida pelo respectivo nome de arquivo após a vírgula (modifique a parte ", $ 1" sob eco para alterar a formatação ou removê-la, se desnecessário). A saída de amostra que pesquisa a string de pesquisa '10' em arquivos com um prefixo de nome "arquivo" é a seguinte:

[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 
Dmitry Aleks
fonte
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] }}'

Este usa GNU grep's -He -nopções para sempre imprimir tanto o nome do arquivo eo linenumber de todas as partidas, então ele classifica pelo nome do arquivo e linenumber, e tubos de TI em awk, que armazena o último jogo para cada arquivo em uma matriz e, eventualmente, impressões isto.

Um método de força bruta, mas funciona.

cas
fonte