Comment utiliser l'option '-prune' de 'trouver' dans sh?

219

Je ne comprends pas très bien l'exemple donné par le man find, quelqu'un peut-il me donner des exemples et des explications? Puis-je y combiner une expression régulière?


La question la plus détaillée est la suivante:

Écrivez un script shell changeall, qui a une interface comme changeall [-r|-R] "string1" "string2". Il trouvera tous les fichiers avec un suffixe .h, .C, .ccou .cppchanger toutes les occurrences string1à string2. -rest une option pour rester dans le répertoire actuel uniquement ou inclure les sous-répertoires.

REMARQUE:

  1. Pour les cas non récursifs, lsn'est PAS autorisé, nous ne pouvons utiliser que findet sed.
  2. J'ai essayé find -depthmais ce n'était PAS pris en charge. C'est pourquoi je me demandais si cela -prunepouvait aider, mais je n'ai pas compris l'exemple de man find.

EDIT2: Je faisais un devoir, je n'ai pas posé de question dans les moindres détails car j'aimerais le terminer moi-même. Comme je l'ai déjà fait et remis, je peux maintenant formuler toute la question. De plus, j'ai réussi à terminer le devoir sans utiliser -prune, mais j'aimerais quand même l'apprendre.

derrdji
la source

Réponses:

438

Ce que j'avais trouvé confus, -prunec'est que c'est une action (comme -print), pas un test (comme -name). Il modifie la liste "à faire", mais renvoie toujours vrai .

Le schéma général d'utilisation -pruneest le suivant :

find [path] [conditions to prune] -prune -o \
            [your usual conditions] [actions to perform]

Vous voulez à peu près toujours le -o(OU logique) immédiatement après -prune, car cette première partie du test (jusqu'à et y compris -prune) retournera faux pour les choses que vous voulez réellement (c'est-à-dire: les choses que vous ne voulez pas tailler).

Voici un exemple:

find . -name .snapshot -prune -o -name '*.foo' -print

Cela trouvera les fichiers "* .foo" qui ne se trouvent pas dans les répertoires ".snapshot". Dans cet exemple, -name .snapshotconstitue le [conditions to prune], et -name '*.foo' -printest [your usual conditions]et [actions to perform].

Remarques importantes :

  1. Si tout ce que vous voulez faire, c'est imprimer les résultats que vous pourriez avoir l'habitude de laisser de côté -print. Vous ne voulez généralement pas le faire lors de l'utilisation -prune.

    Le comportement par défaut de find est de "et" l' expression entière avec l' -printaction s'il n'y a aucune action autre que -prune(ironiquement) à la fin. Cela signifie que l'écriture de ceci:

    find . -name .snapshot -prune -o -name '*.foo'              # DON'T DO THIS

    équivaut à écrire ceci:

    find . \( -name .snapshot -prune -o -name '*.foo' \) -print # DON'T DO THIS

    ce qui signifie qu'il affichera également le nom du répertoire que vous élaguez, ce qui n'est généralement pas ce que vous voulez. Au lieu de cela, il est préférable de spécifier explicitement l' -printaction si c'est ce que vous voulez:

    find . -name .snapshot -prune -o -name '*.foo' -print       # DO THIS
  2. Si votre "condition habituelle" correspond à des fichiers qui correspondent également à votre condition d'élagage, ces fichiers ne seront pas inclus dans la sortie. La façon de résoudre ce problème consiste à ajouter un -type dprédicat à votre condition de pruneau.

    Par exemple, supposons que nous voulions éliminer tous les répertoires commençant par .git(cela est certes quelque peu artificiel - normalement, il vous suffit de supprimer la chose nommée exactement .git ), mais à part cela, nous voulions voir tous les fichiers, y compris les fichiers comme .gitignore. Vous pourriez essayer ceci:

    find . -name '.git*' -prune -o -type f -print               # DON'T DO THIS

    Cela ne serait pas inclus .gitignoredans la sortie. Voici la version fixe:

    find . -name '.git*' -type d -prune -o -type f -print       # DO THIS

Conseil supplémentaire: si vous utilisez la version GNU de find, la page texinfo pour finda une explication plus détaillée que sa page de manuel (comme c'est le cas pour la plupart des utilitaires GNU).

Laurence Gonsalves
la source
6
ce n'est pas évident à 100% dans votre texte (mais comme vous n'imprimez que '* .foo', il n'y a pas de conflit) mais la partie -prune n'imprimera rien non plus (pas seulement les répertoires) nommé ".snapshot". c'est-à-dire, -prunene fonctionne pas seulement sur les répertoires (mais, pour les répertoires, il empêche également d'entrer les répertoires correspondant à cette condition, c'est-à-dire ici les répertoires correspondant à cela -name .snapshot).
Olivier Dulac
12
et +1 pour vous pour l'explication bien faite (et surtout la note importante). Vous devriez soumettre ceci aux développeurs de recherche (car la page de manuel n'explique pas "pruneau" pour les êtres humains normaux ^^ Il m'a fallu beaucoup d'essais pour le comprendre, et je n'ai pas vu cet effet secondaire dont vous nous avertissez)
Olivier Dulac
2
@OlivierDulac C'est un très bon point sur la suppression potentielle des fichiers que vous souhaitez conserver. J'ai mis à jour la réponse pour clarifier cela. ce n'est pas vraiment -prunelui-même qui en est la cause. Le problème est que l'opérateur ou "court-circuite", et ou a une priorité inférieure à et. Le résultat final est que si un fichier appelé .snapshotest rencontré, il correspondra au premier -name, -prunene fera rien (mais retournera vrai), puis le ou retournera vrai puisque son argument de gauche était vrai. L'action (par exemple:) -printfait partie de son deuxième argument, elle n'a donc aucune chance de s'exécuter.
Laurence Gonsalves
3
+1 a finalement découvert pourquoi j'avais besoin -printà la fin, je peux maintenant arrêter d'ajouter \! -path <pattern>en plus de-prune
Variable misérable
6
Notez que "-o" est un raccourci pour "-ou", qui (bien qu'il ne soit pas compatible POSIX) se lit plus clairement.
yoyo
27

Normalement, la façon dont nous faisons les choses sous Linux et la façon dont nous pensons est de gauche à droite.
Donc, vous iriez d'abord écrire ce que vous cherchez:

find / -name "*.php"

Ensuite, vous appuyez probablement sur Entrée et réalisez que vous obtenez trop de fichiers à partir de répertoires que vous ne souhaitez pas. Excluons / media pour éviter de rechercher vos lecteurs montés.
Vous devez maintenant simplement ajouter ce qui suit à la commande précédente:

-print -o -path '/media' -prune

donc la commande finale est:

find / -name "*.php" -print -o -path '/media' -prune

............... | <--- Inclure ---> | .................... | <- -------- Exclure ---------> |

Je pense que cette structure est beaucoup plus facile et correspond à la bonne approche

AmitP
la source
3
Je ne m'attendais pas à ce que cela soit efficace - j'aurais pensé qu'il évaluerait la clause de gauche d'abord avant le pruneau, mais à ma grande surprise, un test rapide semble suggérer qu'il findest assez intelligent pour traiter la -pruneclause en premier. Hmmm, intéressant.
artfulrobot
Je n'avais jamais pensé que depuis près d'une décennie d'utilisation de GNU find! Merci pour ça! Cela va définitivement changer ma façon de penser à -prunepartir de maintenant.
Felipe Alvarez
3
@artfulrobot Est-ce vraiment le traitement en premier? J'aurais pensé qu'il entre /media, notant qu'il n'est pas appelé *.phppuis vérifiant s'il est actuellement à l'intérieur /media, voyant qu'il l'est et donc sautant tout ce sous-arbre. C'est toujours de gauche à droite, cela ne fait aucune différence tant que les deux vérifications ne se chevauchent pas.
phk
26

Attention, -prune n'empêche pas de descendre dans n'importe quel répertoire comme certains l'ont dit. Il empêche de descendre dans des répertoires correspondant au test auquel il est appliqué. Peut-être que quelques exemples aideront (voir le bas pour un exemple d'expression régulière). Désolé pour le fait que ce soit si long.

$ find . -printf "%y %p\n"    # print the file type the first time FYI
d .
f ./test
d ./dir1
d ./dir1/test
f ./dir1/test/file
f ./dir1/test/test
d ./dir1/scripts
f ./dir1/scripts/myscript.pl
f ./dir1/scripts/myscript.sh
f ./dir1/scripts/myscript.py
d ./dir2
d ./dir2/test
f ./dir2/test/file
f ./dir2/test/myscript.pl
f ./dir2/test/myscript.sh

$ find . -name test
./test
./dir1/test
./dir1/test/test
./dir2/test

$ find . -prune
.

$ find . -name test -prune
./test
./dir1/test
./dir2/test

$ find . -name test -prune -o -print
.
./dir1
./dir1/scripts
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.sh
./dir1/scripts/myscript.py
./dir2

$ find . -regex ".*/my.*p.$"
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.py
./dir2/test/myscript.pl

$ find . -name test -prune -regex ".*/my.*p.$"
(no results)

$ find . -name test -prune -o -regex ".*/my.*p.$"
./test
./dir1/test
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.py
./dir2/test

$ find . -regex ".*/my.*p.$" -a -not -regex ".*test.*"
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.py

$ find . -not -regex ".*test.*"                   .
./dir1
./dir1/scripts
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.sh
./dir1/scripts/myscript.py
./dir2
En pause jusqu'à nouvel ordre.
la source
si vous "touchez également ./dir1/scripts/test" (c'est-à-dire, avez un fichier "test", et non dir, dans ce sous-répertoire imprimé), il ne sera pas imprimé par le find . -name test -prune -o -print: iow, -pruneest une action qui travaille sur fichiers
Olivier Dulac
10

Ajout aux conseils donnés dans d'autres réponses (je n'ai pas de représentant pour créer des réponses) ...

Lors de la combinaison -pruneavec d'autres expressions, il existe une différence subtile de comportement selon les autres expressions utilisées.

L'exemple de @Laurence Gonsalves trouvera les fichiers "* .foo" qui ne se trouvent pas dans les répertoires ".snapshot": -

find . -name .snapshot -prune -o -name '*.foo' -print

Cependant, ce raccourci légèrement différent .snapshotlistera , peut-être par inadvertance, le répertoire (et tous les répertoires .snapshot imbriqués): -

find . -name .snapshot -prune -o -name '*.foo'

La raison en est (selon la page de manuel de mon système): -

Si l'expression donnée ne contient aucune des primaires -exec, -ls, -ok ou -print, l'expression donnée est effectivement remplacée par:

(expression_donnée) -print

Autrement dit, le deuxième exemple équivaut à saisir ce qui suit, modifiant ainsi le regroupement des termes: -

find . \( -name .snapshot -prune -o -name '*.foo' \) -print

Cela a au moins été constaté sur Solaris 5.10. Ayant utilisé diverses saveurs de * nix pendant environ 10 ans, je n'ai récemment cherché une raison pour laquelle cela se produit.

crw
la source
Merci d'avoir attiré l'attention sur la différence entre utiliser -pruneavec et sans -print!
mcw
3

L'élagage est un pas récursif à tout changement de répertoire.

Depuis la page de manuel

Si -depth n'est pas donné, true; si le fichier est un répertoire, n'y descendez pas. Si -depth est donné, false; aucun effet.

Fondamentalement, il ne descendra dans aucun sous-répertoire.

Prenez cet exemple:

Vous avez les répertoires suivants

  • / accueil / test2
  • / accueil / test2 / test2

Si vous exécutez find -name test2:

Il renverra les deux répertoires

Si vous exécutez find -name test2 -prune:

Il retournera uniquement / home / test2 car il ne descendra pas dans / home / test2 pour trouver / home / test2 / test2

AdamW
la source
pas 100% vrai: c'est "faire l'élagage lors de la mise en correspondance de la condition, et s'il s'agit d'un répertoire, le retirer de la liste des tâches, c'est-à-dire ne pas l'entrer aussi". -prune fonctionne également sur les fichiers.
Olivier Dulac
2

Je ne suis pas un expert en la matière (et cette page a été très utile avec http://mywiki.wooledge.org/UsingFind )

Nous venons de remarquer -pathun chemin qui correspond entièrement à la chaîne / chemin qui vient juste aprèsfind ( .dans ces exemples) où as -namecorrespond à tous les noms de base.

find . -path ./.git  -prune -o -name file  -print

bloque le répertoire .git dans votre répertoire actuel ( comme votre recherche dans . )

find . -name .git  -prune -o -name file  -print

bloque récursivement tous les sous-répertoires .git.

Notez que ./ c'est extrêmement important !! -pathdoit correspondre à un chemin ancré à . ou tout ce qui vient juste après trouver si vous obtenez des correspondances sans (de l'autre côté du ou ' -o') là probablement pas élagué! J'étais naïvement inconscient de cela et cela m'a mis à utiliser -path quand c'est génial quand vous ne voulez pas tailler tous les sous-répertoires avec le même nom de base: D

sabgenton
la source
Notez que si vous le dites, find bla/vous aurez besoin de -path bla/.git (ou si vous placez un a *à la place, cela ressemblerait plus à -name)
sabgenton
1

Afficher tout, y compris dir lui-même mais pas son long contenu ennuyeux:

find . -print -name dir -prune
devon
la source
0

Si vous lisez toutes les bonnes réponses ici, je comprends maintenant que les éléments suivants renvoient tous les mêmes résultats:

find . -path ./dir1\*  -prune -o -print

find . -path ./dir1  -prune -o -print

find . -path ./dir1\*  -o -print
#look no prune at all!

Mais le dernier prendra beaucoup plus de temps car il recherche toujours tout dans dir1. Je suppose que la vraie question est de savoir comment-or éliminer les résultats indésirables sans réellement les rechercher.

Donc, je suppose que prune signifie que les matchs passés ne sont pas décents mais marquez-les comme terminés ...

http://www.gnu.org/software/findutils/manual/html_mono/find.html "Ceci n'est cependant pas dû à l'effet de l'action '-prune' (qui empêche seulement une nouvelle descente, il ne s'assure pas nous ignorons cet élément.) Au lieu de cela, cet effet est dû à l'utilisation de "-o". Puisque le côté gauche de la condition "ou" a réussi pour ./src/emacs, il n'est pas nécessaire d'évaluer le droit- côté ("-print") pour ce fichier particulier. "

sabgenton
la source
0

findconstruit une liste de fichiers. Il applique le prédicat que vous avez fourni à chacun et renvoie ceux qui réussissent.

Cette idée qui -prunesignifie exclure des résultats était vraiment déroutante pour moi. Vous pouvez exclure un fichier sans élagage:

find -name 'bad_guy' -o -name 'good_guy' -print  // good_guy

Il -prunesuffit de modifier le comportement de la recherche. Si la correspondance actuelle est un répertoire, il dit "hé find, le fichier que vous venez de faire correspondre, ne descendez pas dedans" . Il supprime simplement cet arbre (mais pas le fichier lui-même) de la liste des fichiers à rechercher.

Il doit être nommé -dont-descend.

seeker_of_bacon
la source
0

Il y a pas mal de réponses; certains d'entre eux sont un peu trop lourds en théorie. Je laisserai pourquoi j'avais besoin de pruneau une fois, alors peut-être le besoin en premier / l'exemple type d'explication de est utile à quelqu'un :)

Problème

J'avais un dossier avec environ 20 répertoires de nœuds, chacun ayant son node_modules répertoire comme prévu.

Une fois que vous entrez dans un projet, vous le voyez ../node_modules/module. Mais tu sais comment c'est. Presque chaque module a des dépendances, donc ce que vous regardez ressemble plus àprojectN/node_modules/moduleX/node_modules/moduleZ...

Je ne voulais pas me noyer avec une liste avec la dépendance de la dépendance de ...

Connaître -d n/ -depth n, cela ne m'aurait pas aidé, car le répertoire principal / premier nœud_modules que je voulais pour chaque projet était à une profondeur différente, comme ceci:

Projects/MysuperProjectName/project/node_modules/...
Projects/Whatshisname/version3/project/node_modules/...
Projects/project/node_modules/...
Projects/MysuperProjectName/testProject/november2015Copy/project/node_modules/...
[...]

Comment puis-je obtenir le premier une liste de chemins se terminant au premier node_moduleset passer au projet suivant pour obtenir le même?

Entrer -prune

Lorsque vous ajoutez -prune, vous aurez toujours une recherche récursive standard. Chaque "chemin" est analysé, et chaque trouvaille est crachée et findcontinue à creuser comme un bon gars. Mais c'est creuser pour plus de node_modulesce que je ne voulais pas.

Ainsi, la différence est que , dans l' un de ces chemins différents, -prunevont findarrêter de creuser plus loin dans cette voie particulière quand il a trouvé votre article. Dans mon cas, le node_modulesdossier.

Carles Alcolea
la source