Supprimer tous les fichiers sauf dans un certain sous-répertoire avec find

11

Je souhaite supprimer récursivement tous les fichiers non accessibles depuis un certain temps dans le dossier a, à l'exception de tous les fichiers du sous-dossier b.

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

Cependant, je reçois un message d'erreur:

find: l'action -delete active automatiquement -depth, mais -prune ne fait rien lorsque -depth est en vigueur. Si vous voulez continuer, utilisez simplement explicitement l'option -depth.

L'ajout -depthentraîne l' binclusion de tous les fichiers , ce qui ne doit pas se produire.

Quelqu'un connaît un moyen sûr de faire fonctionner cela?

en avant
la source
@ MichaelKjörling: J'ai jeté un œil à extglob, mais comment inclure tout sous asauf a/b?
4
Ne cd a && ls -d !(b/*)fonctionnera pas ? (Pour le faire, rm -rplutôt que ls -d.)
un CV
Votre suggestion supprime les sous-dossiers. Je veux garder les dossiers intacts. Je veux rechercher et supprimer tous les fichiers de l'arborescence sous a(sauf les fichiers sous a/b).
4
Il suffit donc de sauter le -rrm. Il semble que ce que vous demandez est assez facilement répondu en utilisant le globbing étendu de bash, et ensuite ce que vous faites avec le résultat du globbing dépend de vous.
un CV
@ MichaelKjörling Ce n'est pas parce que les deux problèmes ont des solutions qui ressemblent vaguement que les questions sont en double. La plupart des solutions à chacun des deux problèmes ne résolvent pas l'autre problème.
Gilles 'SO- arrête d'être méchant'

Réponses:

13

TL; DR: la meilleure façon est d'utiliser à la -exec rmplace de -delete.

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

Explication:

Pourquoi find se plaint-il lorsque vous essayez d'utiliser -deleteavec -prune?

Réponse courte: car -deleteimplique -depthet -depthrend -pruneinefficace.

Avant d'arriver à la réponse longue, observons d'abord le comportement de la recherche avec et sans -depth:

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

Il n'y a aucune garantie sur la commande dans un seul répertoire. Mais il y a une garantie qu'un répertoire est traité avant son contenu. Notez foo/avant tout foo/*et foo/baravant tout foo/bar/*.

Cela peut être inversé avec -depth.

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

Notez que maintenant tous foo/*apparaissent avant foo/. Même chose avec foo/bar.

Réponse plus longue:

  • -pruneempêche la recherche de descendre dans un répertoire. En d'autres termes, -prunesaute le contenu du répertoire. Dans votre cas, -name b -pruneempêche la recherche de descendre dans n'importe quel répertoire portant le nom b.
  • -depthfait rechercher pour traiter le contenu d'un répertoire avant le répertoire lui-même. Cela signifie qu'au moment où find parvient à traiter l'entrée de répertoire, bson contenu a déjà été traité. Ainsi -pruneest inefficace avec -depthen vigueur.
  • -deleteimplique -depthqu'il peut d'abord supprimer les fichiers, puis le répertoire vide. -deleterefuse de supprimer les répertoires non vides. Je suppose qu'il serait possible d'ajouter une option pour forcer -deleteà supprimer les répertoires non vides et / ou empêcher -deleted'impliquer -depth. Mais c'est une autre histoire.

Il existe une autre façon de réaliser ce que vous voulez:

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

Cela peut ou non être plus facile à retenir.

Cette commande descend toujours dans le répertoire bet traite chaque fichier qu'il contient uniquement pour -notles rejeter. Cela peut être un problème de performances si le répertoire best énorme.

-pathfonctionne différemment de -name. -namene correspond qu'au nom (du fichier ou du répertoire) tandis qu'il -pathcorrespond au chemin entier. Par exemple, observez le chemin /home/lesmana/foo/bar. -name -barcorrespondra car le nom est bar. -path "*/foo*"correspondra car la chaîne se /footrouve dans le chemin. -patha quelques subtilités que vous devez comprendre avant de l'utiliser. Lisez la page de manuel de findpour plus de détails.

Attention, ce n'est pas infaillible à 100%. Il y a des chances de "faux positifs". La façon dont la commande est écrite ci-dessus ignorera tout fichier contenant un répertoire parent dont le nom commence par b(positif). Mais il ignorera également tout fichier dont le nom commence par, bquelle que soit sa position dans l'arbre (faux positif). Cela peut être corrigé en écrivant une meilleure expression que "*/b*". Cela reste un exercice pour le lecteur.

Je suppose que vous avez utilisé aet bcomme espaces réservés et les vrais noms ressemblent plus à allosauruset brachiosaurus. Si vous mettez brachiosaurusen place, bla quantité de faux positifs sera considérablement réduite.

Au moins, les faux positifs ne seront pas supprimés, ce ne sera donc pas aussi tragique. De plus, vous pouvez vérifier les faux positifs en exécutant d'abord la commande sans -delete(mais n'oubliez pas de placer l'implicite -depth) et examinez la sortie.

find a -not -path "*/b*" -type f -depth
lesmana
la source
-not -pathétait juste la chose! Merci pour une explication généreuse!
4
1
Certains élaboration pourquoi -not -pathfonctionne tout -prunene serait pas utile. Pourquoi peut -not -pathcoexister avec -depth?
Faheem Mitha
3

Utilisez simplement rmau lieu de -delete:

find a -name b -prune -o -type f -exec rm -f {} +
Stéphane Chazelas
la source
1
Pouvez-vous expliquer pourquoi cela rmfonctionne et deletene fonctionne pas?
Faheem Mitha
1
Oh, je suppose que peut-être parce que "-delete refuse de supprimer les répertoires non vides.", Pour citer @lesmana. Refuse donc de supprimer les répertoires non vides. Mais rmn'a pas ce problème. Mais, malgré tout, l'élaboration serait une bonne chose.
Faheem Mitha
@FaheemMitha, la réponse à cela est dans la question. -deleteimplique -depth, qui ne peut évidemment pas travailler avec -prune. -pathfonctionne, mais ne s'arrête pas findde descendre dans des répertoires qu'il n'a pas besoin d'explorer.
Stéphane Chazelas
0

Les réponses et explications ci-dessus ont été très utiles.

J'utilise les solutions de contournement de "-exec rm {} +" ou "-not -path ... -delete", mais celles-ci peuvent être beaucoup plus lentes que "find ... -delete". J'ai vu "find ... -delete "s'exécute 5 fois plus vite que" -exec rm {} + "sur les répertoires profonds d'un système de fichiers NFS.

La solution '-not path "a la surcharge évidente de regarder tous les fichiers dans les répertoires exclus et ci-dessous.

Le "find .. -exec rm {} +" appelle rm qui effectue les appels système:

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

Le "find -delete" fait des appels système:

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

Ainsi, la commande "-exec rm {} +" rm effectue le chemin complet de la recherche d'inode deux fois par fichier, mais "find -delete" effectue une statistique et dissocie le nom de fichier dans le répertoire en cours. C'est une grande victoire lorsque vous supprimez un grand nombre de fichiers dans un répertoire.

(mode pleurnicher activé (désolé))

Il semble que la conception de l'interaction entre -depth, -delete et -prune élimine inutilement la manière la plus efficace de faire l'action courante "supprimer les fichiers sauf ceux dans les répertoires -prune"

La combinaison de "-type f -delete" devrait pouvoir s'exécuter sans -depth car elle n'essaye pas de supprimer des répertoires. Alternativement, si "find" avait une action "-deletefile" qui dit de ne pas supprimer les répertoires, -depth n'aurait pas besoin d'être impliqué.

Les appels xargs ou find -exec à la commande rm pourraient être accélérés si rm avait une option pour trier les noms de fichiers, ouvrir les répertoires et ne pas lier (dir_fd, nom de fichier) au lieu de dissocier les chemins complets. Il fait déjà le lien (dir_fd, nom de fichier) lors de la récursivité dans les répertoires avec l'option -r.

(mode pleurnicher désactivé)

Donald Mears
la source