Trova l'ultima occorrenza della stringa in più file


9

Devo cercare più file di registro (tutti i file generati nelle ultime 24 ore, tutti mantenuti nella stessa directory) per trovare l'ultima occorrenza di una stringa. Questo è il comando che ho scritto:

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

Ma questo restituisce solo l'ultima riga per un file. Qualche suggerimento su come modificare questo per ottenere tutte le linee?


hai provato a invertire la coda e l'ultimo grep? trova . -mtime 1 | grep fileprefix | xargs tail -1 | grep 'search string'
Mathieu

Risposte:


4

Supponendo strutture GNU:

find . -mtime -1 -exec bash -c \
'for f; do tac "$f" | grep -m1 fileprefix; done' _ {} +

Potete per favore elaborare lo scopo di 'bash -c \' dato che sto già usando la shell bash. Scopo anche di "_ {} +" alla fine.
Lokesh,

@Lokesh, è possibile findeseguire comandi sui file utilizzando -exec. Con bash -c, stiamo generando una bashshell che scorre attraverso i file trovati finded esegue tac .. | grep -m1 fileprefixsu ciascuno di essi
iruvar,

Stavo cercando di estendere il filtro delle stringhe per il ciclo includendo il comando cut, cioè per f; tac "$ f" | grep -m1 fileprefix | cut -d '' -f4,7-8 ma nel momento in cui ho messo il comando cut mi dà un errore inaspettato fine del file. Potete per favore suggerire cosa sto facendo di sbagliato.
Lokesh,

@lokesh, utilizzare -d" "con il taglio.
Virgolette

1
Il findcomando può filtrare per il prefisso del file; il grepnon dovrebbe essere necessario per questo. È anche sorprendente che la stringa di ricerca non compaia in questa risposta.
Jonathan Leffler,

8

Se tutto è in una singola directory, puoi fare:

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

Se si tratta di file di grandi dimensioni, potrebbe valere la pena accelerare le cose utilizzando tacper stampare il file in ordine inverso (prima l'ultima riga) e quindi grep -m1per abbinare la prima occorrenza. In questo modo, eviti di leggere l'intero file:

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

Entrambi presuppongono che non ci siano corrispondenze di directory fileprefix. Se ci sono, otterrai un errore che puoi semplicemente ignorare. Se questo è un problema, controlla solo i file:

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

Se è necessario anche stampare il nome del file, aggiungere -Ha ogni grepchiamata. Oppure, se il tuo grepnon lo supporta, digli di cercare anche attraverso /dev/null. Ciò non cambierà l'output ma dato che grepvengono dati più file, stamperà sempre il nome del file per ogni hit:

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

"In questo modo, eviti di leggere l'intero file" - eh? No, eviti di leggere l'intero file in grep ma invece metti l'intero file in tac. Non è chiaro per me che questo sarebbe più veloce, sebbene dipenda dal fatto che la corrispondenza fosse vicina all'inizio o alla fine del file.
Gilles 'SO- smetti di essere malvagio' il

@Gilles no, non metti nemmeno l'intero file tac. Esce non appena viene trovata la prima corrispondenza. Ho appena provato con un file di testo 832M e un modello trovato nell'ultima riga. grep -m 1 pattern filestrumento ~ 7 secondi e tac file | grep -m1 patternimpiegato 0.009.
terdon

4
find . ! -name . -prune -mtime 1 -name 'fileprefix*' \
     -exec sed -se'/searchstring/h;$!d;x' {} +

... funzionerà se si dispone di GNU sedche supporta l' -sopzione di file eparate e un POSIX find.

Probabilmente dovresti aggiungere i qualificatori ! -type do -type f, tuttavia, perché il tentativo di leggere una directory non sarà molto utile e restringere ulteriormente l'intervallo ai file regolari potrebbe evitare una lettura sospesa su una pipe o un file di dispositivo seriale.

La logica è incredibilmente semplice: sedsovrascrive il suo hvecchio spazio con una copia di qualsiasi riga di input che corrisponde searchstring, quindi delimina dall'output tutte le righe di input tranne l'ultima per ciascun file di input. Quando arriva all'ultima riga, xcambia i suoi spazi di attesa e modello, e quindi se è searchstringstato trovato durante la lettura del file, l'ultima occorrenza verrà stampata automaticamente sull'output, altrimenti scrive una riga vuota. (aggiungi /./!dalla coda dello sedscript se ciò è indesiderabile) .

Ciò comporterà un'unica sedchiamata per circa 65k file di input, o qualunque sia il tuo ARG_MAXlimite. Questa dovrebbe essere una soluzione molto performante ed è semplicemente implementata.

Se vuoi anche i nomi dei file, dato un GNU recente sedpuoi scriverli in righe separate con il Fcomando, oppure puoi farli stampare findin un elenco separato per batch aggiungendo il -printprimario dopo +.


1

Che ne dite di:

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

Quanto sopra ti dà un buon output con l'ultima occorrenza di una stringa di ricerca in ogni file seguita dal rispettivo nome del file dopo la virgola (modifica la parte ", $ 1" sotto l'eco per cambiare la formattazione o rimuoverla se non necessario). L'output di esempio che cerca la stringa di ricerca '10' nei file con un prefisso "file" è il seguente:

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

Questo utilizza GNU greps' -He -nle opzioni per stampare sempre sia il nome del file e il linenumber di tutte le partite, allora ordina dal nome del file e linenumber, e la trasporta in awk, che memorizza l'ultima partita per ogni nome di file in un array, e, infine, le stampe esso.

Un metodo abbastanza bruto, ma funziona.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.