Encuentra la última aparición de cadena en varios archivos

9

Necesito buscar múltiples archivos de registro (todos los archivos generados en las últimas 24 horas, todos guardados en el mismo directorio) para encontrar la última aparición de una cadena. Este es el comando que escribí:

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

Pero esto solo devuelve la última línea para un archivo. ¿Alguna sugerencia sobre cómo ajustar esto para obtener todas las líneas?

Lokesh
fuente
¿Intentaste invertir la cola y el último grep? encontrar . -mtime 1 | grep fileprefix | xargs tail -1 | grep 'cadena de búsqueda'
Mathieu

Respuestas:

4

Asumiendo instalaciones GNU:

find . -mtime -1 -exec bash -c \
'for f; do tac "$f" | grep -m1 fileprefix; done' _ {} +
iruvar
fuente
¿Puedes por favor elaborar el propósito de 'bash -c \' ya que ya estoy usando bash shell? También propósito de '_ {} +' al final.
Lokesh
@Lokesh, puedes findejecutar comandos en archivos usando -exec. Con bash -c, estamos generando un bashshell que recorre los archivos encontrados findy se ejecuta tac .. | grep -m1 fileprefixen cada uno
iruvar
Estaba tratando de extender el filtrado de cadenas en el bucle incluyendo el comando cortar, es decir, para f; hacer tac "$ f" | grep -m1 fileprefix | cut -d '' -f4,7-8 pero en el momento en que coloco el comando cut me da un error inesperado al final del archivo. ¿Me puede sugerir qué estoy haciendo mal?
Lokesh
@lokesh, usar -d" "con corte.
Comillas
1
El findcomando puede filtrar por el prefijo del archivo; el grepno debe ser necesario para eso. También es sorprendente que la cadena de búsqueda no figure en esta respuesta.
Jonathan Leffler
8

Si todo está en un solo directorio, puede hacer:

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

Si se trata de archivos grandes, podría valer la pena acelerar el proceso tacal imprimir el archivo en orden inverso (última línea primero) y luego grep -m1para que coincida con la primera aparición. De esa manera, evita tener que leer todo el archivo:

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

Ambos asumen que no hay directorios que coincidan fileprefix. Si lo hay, obtendrá un error que puede ignorar. Si eso es un problema, busca solo archivos:

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

Si también necesita imprimir el nombre del archivo, agréguelo -Ha cada grepinvocación. O, si grepno lo admite, dígale que también busque /dev/null. Eso no cambiará la salida, pero como grepse le dan múltiples archivos, siempre imprimirá el nombre del archivo para cada hit:

for file in *fileprefix*; do
    grep 'search string' "$file" /dev/null | tail -1
done
terdon
fuente
"De esa manera, evitas tener que leer todo el archivo" - ¿eh? No, evitas leer todo el archivo en grep pero en su lugar pones todo el archivo en tac. No está claro para mí que esto sería más rápido, aunque dependería de si la coincidencia estaba cerca del comienzo o del final del archivo.
Gilles 'SO- deja de ser malvado'
@Gilles no, tampoco pones todo el archivo tac. Saldrá tan pronto como se encuentre el primer partido. Acabo de probar con un archivo de texto 832M y un patrón encontrado en la última línea. grep -m 1 pattern fileherramienta ~ 7 segundos y tac file | grep -m1 patterntomó 0.009.
terdon
4
find . ! -name . -prune -mtime 1 -name 'fileprefix*' \
     -exec sed -se'/searchstring/h;$!d;x' {} +

... funcionará si tiene GNU sedque admite la -sopción de archivos separados y un POSIX find.

Probablemente debería agregar los ! -type do -type fcalificadores, sin embargo, porque tratar de leer un directorio No será muy útil, y estrechando aún más la gama de archivos normales podría evitar un colgante de lectura en una tubería o un archivo de dispositivo serie.

La lógica es increíblemente simple: sedsobrescribe su hantiguo espacio con una copia de cualquier línea de entrada que coincida searchstring, luego delige de la salida todas las líneas de entrada, pero la última para cada archivo de entrada. Cuando llega a la última línea, xcambia sus espacios de retención y de patrón, por lo que si searchstringse encuentra en absoluto mientras lee el archivo, la última aparición de este tipo se imprimirá automáticamente en la salida, de lo contrario, escribirá una línea en blanco. (agregue /./!da la cola de la sedsecuencia de comandos si eso no es deseable) .

Esto hará una sola sedinvocación por cada 65k archivos de entrada, o sea cual sea su ARG_MAXlímite. Esta debería ser una solución muy eficaz, y se implementa de manera bastante simple.

Si también desea los nombres de archivo, dado un GNU reciente sed, puede escribirlos en líneas separadas con el Fcomando, o bien puede imprimirlos finden una lista separada por lote agregando el -printprimario después +.

mikeserv
fuente
1

Qué tal si:

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

Lo anterior le brinda una buena salida con la última aparición de una cadena de búsqueda en cada archivo seguido del nombre del archivo respectivo después de la coma (modifique la parte ", $ 1" bajo echo para cambiar el formato o eliminarlo si no es necesario). El resultado de muestra que busca la cadena de búsqueda '10' en archivos con un prefijo de nombre "archivo" es el siguiente:

[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
fuente
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] }}'

Utiliza greplas opciones -Hy -nopciones de GNU para imprimir siempre el nombre de archivo y el número de lino de todas las coincidencias, luego lo ordena por nombre de archivo y número de lino, y lo canaliza a awk, que almacena la última coincidencia para cada nombre de archivo en una matriz, y finalmente imprime eso.

Un método bastante de fuerza bruta, pero funciona.

cas
fuente