2012-03-31 33 views
5

Ho una lista contenente circa 1000 nomi di file da cercare in una directory e nelle sue sottodirectory. Ci sono centinaia di sottodir con più di 1.000.000 di file. Il seguente comando eseguirà find per 1000 volte:Shell: trova i file in un elenco in una directory

cat filelist.txt | while read f; do find /dir -name $f; done 

C'è un modo molto più veloce per farlo?

risposta

9

Se filelist.txt ha un unico nome di file per linea:

find /dir | grep -f <(sed 's@^@/@; s/$/$/; s/\([\.[\*]\|\]\)/\\\1/g' filelist.txt) 

(L'opzione -f significa che le ricerche di grep per tutti i modelli presenti nel file specificato.)

Spiegazione di <(sed '[email protected]^@/@; s/$/$/; s/\([\.[\*]\|\]\)/\\\1/g' filelist.txt):

Il <(...) è chiamato process subsitution ed è un po 'simile a $(...). La situazione è equivalente a (ma usando la sostituzione di processo è più ordinato e forse un po 'più veloce):

sed '[email protected]^@/@; s/$/$/; s/\([\.[\*]\|\]\)/\\\1/g' filelist.txt > processed_filelist.txt 
find /dir | grep -f processed_filelist.txt 

La chiamata al sed esegue i comandi [email protected]^@/@, s/$/$/ e s/\([\.[\*]\|\]\)/\\\1/g su ogni linea di filelist.txt e li stampa. Questi comandi convertono i nomi dei file in un formato che funzionerà meglio con grep.

  • [email protected]^@/@ mezzo messo un / alla prima di ogni nome di file. (Il ^ significa "inizio della linea" in un'espressione regolare)
  • s/$/$/ significa mettere uno $ alla fine di ogni nome file. (Il primo $ significa "fine riga", il secondo è solo un valore letterale $ che viene quindi interpretato da grep per "fine riga").

La combinazione di queste due regole significa che grep cercherà solo per le partite come .../<filename>, in modo che a.txt non corrisponde ./a.txt.backup o ./abba.txt.

s/\([\.[\*]\|\]\)/\\\1/g mette un \ prima di ogni occorrenza di .[] o *. Grep usa regex e quei caratteri sono considerati speciali, ma vogliamo che siano chiari, quindi dobbiamo sfuggire a loro (se non li sfuggiamo, allora un nome di file come a.txt potrebbe corrispondere a file come abtxt).

Ad esempio:

$ cat filelist.txt 
file1.txt 
file2.txt 
blah[2012].txt 
blah[2011].txt 
lastfile 

$ sed '[email protected]^@/@; s/$/$/; s/\([\.[\*]\|\]\)/\\\1/g' filelist.txt 
/file1\.txt$ 
/file2\.txt$ 
/blah\[2012\]\.txt$ 
/blah\[2011\]\.txt$ 
/lastfile$ 

Grep quindi utilizza ogni riga di tale uscita come un pattern Mentre cerca l'uscita di find.

+0

Grazie! cosa significa <(sed 's @^@/@; s/$/$ /; s /\./\\//' filelist.txt ') significa? – Dagang

+0

@Todd, ho ampliato la mia risposta :) – huon

+1

Non dovresti fare di tutto, cercando di ricreare i pattern a livello di programmazione. È soggetto a errori e spesso ci sono alcune zone grigie o possibilità di estensioni nelle specifiche del linguaggio del pattern. In questo caso particolare, penso che sarebbe meglio usare semplicemente 'grep -F -f FILE' –

2

Utilizzare xargs(1) per il ciclo while può essere un po 'più veloce rispetto a bash.

Ti piace questa

xargs -a filelist.txt -I filename find /dir -name filename 

attenzione se i nomi dei file in filelist.txt contiene spazi bianchi, leggi il secondo paragrafo nella sezione DESCRIZIONE di xargs(1) manpage su questo problema.

Un miglioramento basato su alcune ipotesi. Ad esempio, a.txt è in filelist.txt e puoi assicurarti che ci sia solo un a.txt in/dir. Quindi puoi dire allo find(1) di uscire presto quando trova l'istanza.

xargs -a filelist.txt -I filename find /dir -name filename -print -quit 

Un'altra soluzione. Puoi pre-elaborare filelist.txt, trasformarlo in un elenco di argomenti find(1) come questo. Ciò consentirà di ridurre find(1) invocazioni:

find /dir -name 'a.txt' -or -name 'b.txt' -or -name 'c.txt' 
0

io non sono del tutto sicuro della questione qui, ma sono venuto a questa pagina dopo aver tentato di trovare un modo per scoprire che 4 su 13000 file non erano riusciti a copiare.

Nessuna delle risposte ha fatto per me, così ho fatto questo:

cp file-list file-list2 
find dir/ >> file-list2 
sort file-list2 | uniq -u 

che ha portato con un elenco dei file 4 di cui avevo bisogno.

L'idea è di combinare i due elenchi di file per determinare le voci univoche. sort viene utilizzato per creare voci duplicate adiacenti tra loro, che è l'unico modo per filtrarle da uniq.

0

Se filelist.txt è un elenco semplice:

$ find /dir | grep -F -f filelist.txt 

Se filelist.txt è una lista modello:

$ find /dir | grep -f filelist.txt 
Problemi correlati