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

8

Eu 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 eu 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?

    
por Lokesh 05.12.2015 / 08:02

5 respostas

4

Assumindo as facilidades do GNU:

find . -mtime -1 -exec bash -c \
'for f; do tac "$f" | grep -m1 fileprefix; done' _ {} +
    
por 05.12.2015 / 08:22
8

Se tudo estiver em um único diretório, você poderia fazer:

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

Se forem arquivos grandes, pode valer a pena acelerar o processo usando tac para imprimir o arquivo em ordem reversa (última linha primeiro) e, em seguida, grep -m1 para corresponder à 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 supõem que não há diretórios correspondentes a fileprefix . Se houver, você receberá um erro que você pode simplesmente ignorar. 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 imprimir o nome do arquivo, adicione -H a cada grep invocação. Ou, se o seu grep não for compatível, peça para pesquisar também em /dev/null . Isso não alterará a saída, mas como grep recebe vários arquivos, sempre será impresso o nome de cada hit:

for file in *fileprefix*; do
    grep 'search string' "$file" /dev/null | tail -1
done
    
por 05.12.2015 / 12:16
4
find . ! -name . -prune -mtime 1 -name 'fileprefix*' \
     -exec sed -se'/searchstring/h;$!d;x' {} +

... funcionará se você tiver o GNU sed que suporta a opção -s eparate files e um POSIX find .

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

A lógica é incrivelmente simples - sed sobrescreve seu espaço h old com uma cópia de qualquer linha de entrada que corresponda a searchstring , então d eletiza da saída todas as linhas de entrada, mas a última para cada arquivo de entrada. Quando chega à última linha, e x altera seus espaços de retenção e padrão, e assim, se searchstring for encontrado enquanto lê o arquivo, a última ocorrência será impressa automaticamente na saída, senão ele grava um espaço em branco. linha. (adicione /./!d ao final do script sed se isso não for desejado) .

Isso fará uma única invocação de sed para alguns arquivos de entrada de 65k - ou seja qual for o limite de ARG_MAX . Esta deve ser uma solução muito eficaz e é simplesmente implementada.

Se você também quiser os nomes dos arquivos, dado um% GNU sed você pode escrevê-los em linhas separadas com o comando F , ou então pode imprimi-los por find em uma lista separada por lote anexando o -print primary após + .

    
por 05.12.2015 / 09:15
1

Que tal:

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

O texto acima apresenta uma boa saída com a última ocorrência de uma string de pesquisa em cada arquivo, seguida do respectivo nome de arquivo após a vírgula (modifique a parte "$ 1" no echo para alterar a formatação ou removê-la se desnecessário) . A saída de exemplo que procura por uma string de pesquisa '10' em arquivos com um prefixo de nome "file" é 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 
    
por 05.12.2015 / 08:32
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] }}'

Isso usa as opções grep e -H do GNU -n para sempre imprimir o nome do arquivo e o linenumber de todas as correspondências, depois ordena o nome do arquivo e o linenumber e o canaliza para o awk, que armazena o arquivo. última correspondência para cada nome de arquivo em uma matriz e, eventualmente, imprime.

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

    
por 05.12.2015 / 08:35