Elimina tutti i file tranne in una determinata sottodirectory con find


11

Voglio eliminare in modo ricorsivo tutti i file a cui non si accede da un po 'di tempo nella cartella a, ad eccezione di tutti i file nella sottocartella b.

find a \( -name b -prune \) -o -type f -delete

Tuttavia, viene visualizzato un messaggio di errore:

find: L'azione -delete attiva automaticamente -depth, ma -prune non fa nulla quando -depth è attivo. Se vuoi continuare comunque, usa esplicitamente l'opzione -depth.

L'aggiunta -depthcausa l' binclusione di tutti i file , cosa che non deve accadere.

Qualcuno sa un modo sicuro per farlo funzionare?


@ MichaelKjörling: ho dato un'occhiata a extglob, ma come si include tutto sotto atranne a/b?
pubblicato il

Non cd a && ls -d !(b/*)funziona? (Per farlo, solo rm -rpiuttosto che ls -d.)
un CVn

Il tuo suggerimento elimina le sottocartelle. Voglio mantenere intatte le cartelle. Voglio trovare ed eliminare tutti i file nella struttura ad albero a(tranne i file in a/b).
pubblicato il

Quindi salta il -rto rm. Sembra che ti stia rispondendo abbastanza facilmente a ciò che stai chiedendo usando il globbing esteso di bash, e poi quello che fai con il risultato del globbing dipende da te.
un CVn il

@ MichaelKjörling Solo perché i due problemi hanno soluzioni vagamente simili non rendono le domande un duplicato. La maggior parte delle soluzioni a ciascuno dei due problemi non risolve l'altro problema.
Gilles 'SO- smetti di essere malvagio' il

Risposte:


13

TL; DR: il modo migliore è usare al -exec rmposto di -delete.

find a \( -name b -prune \) -o -type f -exec rm {} +

Spiegazione:

Perché trovare lamentarsi quando si tenta di utilizzare -deletecon -prune?

Risposta breve: perché -deleteimplica -depthe -depthrende -pruneinefficace.

Prima di arrivare alla lunga risposta, osservare innanzitutto il comportamento di find con e senza -depth:

$ find foo/
foo/
foo/f1
foo/bar
foo/bar/b2
foo/bar/b1
foo/f2

Non esiste alcuna garanzia sull'ordine in una singola directory. Ma c'è una garanzia che una directory viene elaborata prima del suo contenuto. Nota foo/prima di qualsiasi foo/*e foo/barprima di qualsiasi foo/bar/*.

Questo può essere invertito con -depth.

$ find foo/ -depth
foo/f2
foo/bar/b2
foo/bar/b1
foo/bar
foo/f1
foo/

Nota che ora foo/*appaiono tutti prima foo/. Lo stesso con foo/bar.

Risposta più lunga:

  • -pruneimpedisce a find di scendere in una directory. In altre parole, -prunesalta il contenuto della directory. Nel tuo caso -name b -pruneimpedisce a find di scendere in qualsiasi directory con il nome b.
  • -depthfa trovare per elaborare il contenuto di una directory prima della directory stessa. Ciò significa che nel momento in cui find riesce a elaborare la voce della directory, il bsuo contenuto è già stato elaborato. Quindi -pruneè inefficace con -depthin effetti.
  • -deleteimplica -depthquindi in grado di eliminare prima i file e poi la directory vuota. -deletesi rifiuta di eliminare le directory non vuote. Immagino che sarebbe possibile aggiungere un'opzione per forzare -deletel'eliminazione di directory non vuote e / o per impedire -deleteche ciò implichi -depth. Ma questa è un'altra storia.

C'è un altro modo per ottenere ciò che vuoi:

find a -not -path "*/b*" -type f -delete

Questo può essere o non essere più facile da ricordare.

Questo comando scende ancora nella directory be procede ogni singolo file in essa contenuto solo per -notrifiutarli. Questo può essere un problema di prestazioni se la directory bè enorme.

-pathfunziona diversamente da -name. -namecorrisponde solo al nome (del file o della directory) mentre -pathcorrisponde all'intero percorso. Ad esempio osservare il percorso /home/lesmana/foo/bar. -name -barcorrisponderà perché il nome è bar. -path "*/foo*"corrisponderà perché la stringa /fooè nel percorso. -pathha alcune complessità che dovresti capire prima di usarlo. Leggi la pagina man di findper maggiori dettagli.

Attenzione che questo non è sicuro al 100%. Ci sono possibilità di "falsi positivi". Il modo in cui il comando è scritto sopra salterà qualsiasi file che ha una directory padre con il nome che inizia b(positivo). Ma salterà anche qualsiasi file con il nome che inizia con bindipendentemente dalla posizione nella struttura (falso positivo). Questo può essere risolto scrivendo un'espressione migliore di "*/b*". Questo è lasciato come esercizio per il lettore.

Presumo che tu abbia usato ae bcome segnaposto e i nomi reali siano più simili allosauruse brachiosaurus. Se lo metti brachiosaurusal posto di ballora la quantità di falsi positivi verrà drasticamente ridotta.

Almeno i falsi positivi non saranno cancellati, quindi non sarà così tragico. Inoltre, è possibile verificare la presenza di falsi positivi eseguendo prima il comando senza -delete(ma ricordarsi di posizionare l'implicito -depth) ed esaminare l'output.

find a -not -path "*/b*" -type f -depth

-not -pathera proprio la cosa! Grazie per una generosa spiegazione!
pubblicato il

1
Qualche elaborazione sul perché -not -pathfunziona mentre -prunenon è utile. Perché può -not -pathcoesistere -depth?
Faheem Mitha,

3

Basta usare rminvece di -delete:

find a -name b -prune -o -type f -exec rm -f {} +

1
Puoi approfondire perché rmfunziona e deletenon funziona?
Faheem Mitha,

1
Oh, immagino forse perché "-delete rifiuta di eliminare le directory non vuote", per citare @lesmana. Quindi si rifiuta di eliminare le directory non vuote. Ma rmnon ha questo problema. Ma, a prescindere, l'elaborazione sarebbe una buona cosa.
Faheem Mitha,

@FaheemMitha, la risposta è nella domanda. -deleteimplica -depth, che ovviamente non può funzionare -prune. -pathfunziona, ma non si ferma finddalla discesa in directory che non ha bisogno di esplorare.
Stéphane Chazelas,

0

Le risposte e le spiegazioni di cui sopra sono state molto utili.

Uso le soluzioni alternative di "-exec rm {} +" o "-not -path ... -delete ', ma quelle possono essere molto più lente di" find ... -delete ". Ho visto" find ... -delete "esegui 5 volte più veloce di" -exec rm {} + "su directory profonde su un filesystem NFS.

La soluzione "-not path" ha l'ovvio sovraccarico di guardare tutti i file nelle directory escluse e sotto.

"Find .. -exec rm {} +" chiama rm che fa chiamate di sistema:

fstatat(AT_FDCWD, path...); 
unlinkat(AT_FDCWD, path, 0)

"Find -delete" esegue le chiamate di sistema:

 fd=open(dir,...);
 fchdir(fd); 
 fstatat(AT_FDCWD, filename,...)
 unlinkat(dirfd, filename,...)

Quindi il comando "-exec rm {} +" rm fa il percorso completo per inode la ricerca due volte due volte per file, ma "find -delete" fa una stat e scollega il nome del file nella directory corrente. Questa è una grande vittoria quando si rimuovono molti file in una directory.

(modalità piagnucola attivata (scusa))

Sembra che la progettazione dell'interazione tra -depth, -delete e -prune elimini inutilmente il modo più efficiente di fare l'azione comune "elimina i file tranne quelli nelle directory -prune"

La combinazione di "-type f -delete" dovrebbe essere in grado di funzionare senza -depth poiché non sta cercando di eliminare le directory. In alternativa, se "find" avesse un'azione "-deletefile" che dice di non cancellare le directory, non sarebbe necessario implicare -depth.

I comandi xargs o find -exec per il comando rm potrebbero essere velocizzati se rm avesse un'opzione per ordinare i nomi di file, aprire le directory e fare unlinkat (dir_fd, nomefile) invece di scollegare i percorsi completi. Fa già lo scollegamento (dir_fd, nomefile) quando ricorre attraverso le directory con l'opzione -r.

(modalità piagnucolare disattivata)

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.