2016-06-07 19 views
5

A volte sono grep -ing migliaia di file e sarebbe bello vedere qualche tipo di progresso (barra o stato).grep - come inviare la barra di avanzamento o lo stato

So che questo non è banale perché grep emette i risultati di ricerca per STDOUT e il mio flusso di lavoro di default è che l'uscita ho i risultati in un file e vorrebbe la barra di avanzamento/stato da emettere a STDOUT o STDERR.

Ciò richiederebbe la modifica del codice sorgente di grep?

comando ideale è:

grep -e "STRING" --results="FILE.txt"

e il progresso:

[curr file being searched], number x/total number of files 

scritto STDOUT o STDERR

+0

Hai mai pensato di utilizzare uno script per farlo? È più semplice che modificare il codice sorgente grep –

risposta

7

Questo non sarebbe necessariamente la modifica grep, anche se probabilmente si potrebbe ottenere una più accurata barra di avanzamento con una tale modifica.

Se si esegue il grepping di "migliaia di file" con una singola chiamata di grep, è molto probabile che si stia utilizzando l'opzione -r in modo ricorsivo in una struttura di directory.In tal caso, non è nemmeno chiaro che grep sa quanti file esaminerà, perché credo che inizi a esaminare i file prima che esplori l'intera struttura di directory. Esplorare prima la struttura delle directory probabilmente aumenterebbe il tempo di scansione totale (e, infatti, c'è sempre un costo per produrre rapporti di progresso, motivo per cui poche utility Unix tradizionali fanno questo.)

In ogni caso, un semplice ma leggermente la barra di avanzamento imprecisa potrebbe essere ottenuta costruendo l'elenco completo dei file da scansionare e quindi alimentandoli a grep in lotti di alcune dimensioni, forse 100, o forse in base alla dimensione totale del batch. I piccoli batch consentirebbero rapporti di avanzamento più accurati, ma aumenterebbero anche il sovraccarico poiché richiederebbero l'avvio del processo in più grep e il tempo di avvio del processo potrebbe essere più di un semplice file. Il report sull'avanzamento verrebbe aggiornato per ogni batch di file, quindi dovresti scegliere una dimensione batch che ti fornisca aggiornamenti regolari senza aumentare troppo il sovraccarico. Basare la dimensione del batch sulla dimensione totale dei file (utilizzando, ad esempio, stat per ottenere il file) renderebbe il report di avanzamento più preciso ma aggiungerà un costo aggiuntivo per l'avvio del processo.

Un vantaggio di questa strategia è che è possibile eseguire anche due o più greps in parallelo, il che potrebbe accelerare un po 'il processo.


In termini generali, un semplice script (che ha appena divide i file dal conte, non in base alle dimensioni, e che non tenta di parallelizzare).

# Requires bash 4 and Gnu grep 
shopt -s globstar 
files=(**) 
total=${#files[@]} 
for ((i=0; i<total; i+=100)); do 
    echo $i/$total >>/dev/stderr 
    grep -d skip -e "$pattern" "${files[@]:i:100}" >>results.txt 
done 

Per semplicità, io uso un globstar (**) per mettere in sicurezza tutti i file in un array. Se la tua versione di bash è troppo vecchia, puoi farlo collegando l'output di find, ma non è molto efficiente se hai molti file. Sfortunatamente, non conosco un modo per scrivere un'espressione globstar che corrisponda solo ai file. (**/ corrisponde solo alle directory). Fortunatamente, GNU grep fornisce l'opzione -d skip che salta silenziosamente le directory. Ciò significa che il conteggio dei file sarà leggermente imprecisa, dal momento che le directory verranno conteggiate, ma probabilmente non fa molta differenza.

Probabilmente vorrai rendere più pulito il rapporto sui progressi utilizzando alcuni codici di console. Quanto sopra è solo per iniziare.

Il modo più semplice per dividere che in diversi processi sarebbe dividere solo l'elenco in diversi segmenti X ed eseguire X diverso per cicli, ciascuno con un diverso punto di partenza. Tuttavia, probabilmente non finiranno tutti allo stesso tempo, quindi non è ottimale. Una soluzione migliore è GNU parallela. Si potrebbe fare qualcosa di simile:

find . -type f -print0 | 
parallel --progress -L 100 -m -j 4 grep -e "$pattern" > results.txt 

(Qui -L 100 specifica che fino a 100 file dovrebbe essere data a ogni istanza grep, e -j 4 specifica quattro processi paralleli Ho appena tirato quei numeri fuori l'aria, si'. ll probabilmente vuole regolarle.)

+0

Risposta molto buona e quasi completa. Per favore pubblica un esempio su come usare i comandi 'find, parallel, grep' per eseguire l'operazione e la contrassegnerò come accettata. – Adrian

+0

@adrian: sarebbe utile sapere come stai invocando grep: la cosa '-r' era solo un'ipotesi. – rici

+0

il mio solito comando grep è 'grep -e" STRING "* -r'. Fare una serie di file * X alla volta è un'idea perfetta. – Adrian

0

Sono abbastanza sicuro che si avrebbe bisogno di modificare il codice sorgente grep. E quei cambiamenti sarebbero enormi.

Attualmente grep non conosce il numero di righe di un file fino a quando non ha terminato l'analisi dell'intero file. Per le tue esigenze, è necessario analizzare il file 2 volte o determinare almeno il numero di riga completo in un altro modo.

La prima volta che determinerebbe il conteggio delle righe per la barra di avanzamento. La seconda volta farebbe effettivamente il lavoro una ricerca per il tuo modello.

Ciò non solo aumenterebbe il tempo di esecuzione ma violerebbe una delle principali filosofie UNIX.

  1. Fare in modo che ogni programma esegua una sola operazione. Per fare un nuovo lavoro, ricostruisci piuttosto che complicare vecchi programmi aggiungendo nuove "funzionalità". (source)

Ci potrebbero essere altri strumenti là fuori per il vostro bisogno, ma per quanto ne so grep non si adatta qui.

+1

OP non dice nulla sul conteggio delle righe, solo sui file.E non è nemmeno chiaro che il conteggio delle righe sarebbe utile; una statistica più semplice da raccogliere sarebbe il numero totale di byte (che è possibile ottenere da una chiamata a una statistica), e anche quella sarebbe una statistica più accurata, poiché grep in realtà legge in blocchi, non in righe. Tuttavia, sono d'accordo con la filosofia di base della tua risposta. – rici

+0

Mi dispiace aver frainteso l'output 'numero x' e un pensiero che intende la riga x nel file y. – cb0

1

io normalmente usare qualcosa di simile a questo:

grep | tee "FILE.txt" | cat -n | sed 's/^/match: /;s/$/  /' | tr '\n' '\r' 1>&2 

Non è perfetto, in quanto visualizza solo le partite, e se a lungo o differiscono di molto in lunghezza ci sono errori, ma dovrebbe fornire tu con l'idea generale.

o un semplice puntini:

grep | tee "FILE.txt" | sed 's/.*//' | tr '\n' '.' 1>&2 
+1

Come indica lo stato? – Adrian

+0

'grep -e" STRING "| tee "FILE.txt" 'si spera sia la risposta al tuo' grep -e "STRING" --results = "FILE.txt" ', ma non è pensato per essere uno stato completo come' x/numero totale di file' . Mostra solo il numero di corrispondenze già elaborate. –

1

Prova il programma parallelo

find * -name \*.[ch] | parallel -j5 --bar '(grep grep-string {})' > output-file 

Anche se ho trovato questo per essere più lento di un semplice

find * -name \*.[ch] | xargs grep grep-string > output-file 
Problemi correlati