Limiter la recherche POSIX à une profondeur spécifique?

15

J'ai remarqué récemment que les spécifications POSIX pourfind ne comprennent pas le -maxdepthprimaire.

Pour ceux qui ne le connaissent pas, l'objectif du -maxdepthprimaire est de limiter le nombre de niveaux profonds findqui descendront. -maxdepth 0entraîne uniquement le traitement des arguments de ligne de commande; -maxdepth 1ne traiterait que les résultats directement dans les arguments de ligne de commande, etc.

Comment puis-je obtenir le comportement équivalent au -maxdepthprimaire non-POSIX en utilisant uniquement les options et outils spécifiés par POSIX?

(Remarque: bien sûr, je peux obtenir l'équivalent de -maxdepth 0simplement en utilisant -prunecomme premier opérande, mais cela ne s'étend pas à d'autres profondeurs.)

Caractère générique
la source
@StevenPenny, FreeBSD -depth -2, -depth 1... l'approche pourrait être considérée comme meilleure que celle de GNU -maxdepth/-mindepth
Stéphane Chazelas
@ StéphaneChazelas dans les deux cas - POSIX find devrait avoir l'un ou l'autre; sinon il est paralysé
Steven Penny
1
Au moins pour -maxdepth/ -mindepth, il existe des alternatives raisonnables (notez qu'il -paths'agit d'un ajout récent à POSIX). Les alternatives pour -timexyou -mtime -3m(ou -mmin -3) sont beaucoup plus lourdes. Certains aiment -execdir/ -deleten'ont pas d'alternative fiable.
Stéphane Chazelas
2
@StevenPenny, n'hésitez pas à enregistrer un ticket sur austingroupbugs.net pour demander son ajout. J'ai vu des choses s'ajouter sans avoir besoin d'un sponsor alors qu'il y avait une justification solide. Une meilleure solution serait probablement de faire en sorte que de nombreuses implémentations l'ajoutent en premier afin que POSIX n'ait qu'à spécifier l'existant qui est généralement moins litigieux.
Stéphane Chazelas
@ StéphaneChazelas dans mon cas j'ai fini par nommer directement les fichiers, mais merci; Je pourrais déposer un ticket si cela se reproduit
Steven Penny

Réponses:

7

Vous pouvez utiliser -pathpour faire correspondre une profondeur donnée et y tailler. Par exemple

find . -path '*/*/*' -prune -o -type d -print

serait maxdepth 1, car *correspond à ., */*correspond à ./dir1et */*/*correspond à l' ./dir1/dir2élagage. Si vous utilisez un répertoire de départ absolu, vous devez également ajouter un début /au -path.

meuh
la source
Hmmm, délicat. Ne pourriez-vous pas simplement supprimer une couche de /*la fin du motif, retirer l' -oopérateur et obtenir le même résultat?
Wildcard
Non, parce que les *correspondances /aussi, donc le dir a/b/c/d/econviendrait -path */*, malheureusement.
meuh
Mais a/b/c/d/ene serait jamais atteint , car -pruneserait appliqué à a/b....
Wildcard
1
Désolé, j'ai mal lu cela -pruneet j'ai -oété supprimé. Si vous gardez le -pruneproblème, c'est que le */*ne correspondra à rien à un niveau supérieur à maxdepth, par exemple le répertoire unique a.
meuh
11

L'approche de @ meuh est inefficace car son -maxdepth 1approche permet toujours de findlire le contenu des répertoires au niveau 1 pour les ignorer ultérieurement autrement. Il ne fonctionnera pas non plus correctement avec certaines findimplémentations (y compris GNU find) si certains noms de répertoire contiennent des séquences d'octets qui ne forment pas de caractères valides dans les paramètres régionaux de l'utilisateur (comme pour les noms de fichiers dans un codage de caractères différent).

find . \( -name . -o -prune \) -extra-conditions-and-actions

est la manière la plus canonique d'implémenter GNU -maxdepth 1(ou FreeBSD -depth -2).

En règle générale, c'est que -depth 1vous voulez ( -mindepth 1 -maxdepth 1) que vous ne voulez pas considérer .(profondeur 0), et c'est encore plus simple:

find . ! -name . -prune -extra-conditions-and-actions

Car -maxdepth 2cela devient:

find . \( ! -path './*/*' -o -prune \) -extra-conditions-and-actions

Et c'est là que vous rencontrez les problèmes de caractères non valides.

Par exemple, si vous avez un répertoire appelé Stéphanemais qui éest encodé dans le jeu de caractères iso8859-1 (aka latin1) (0xe9 octet) comme c'était le plus courant en Europe occidentale et en Amérique jusqu'au milieu des années 2000, alors cet octet 0xe9 n'est pas un caractère valide en UTF-8. Ainsi, dans les paramètres régionaux UTF-8, le *caractère générique (avec certaines findimplémentations) ne correspondra pas Stéphanetel quel *ou plusieurs caractères et 0xe9 n'est pas un caractère.

$ locale charmap
UTF-8
$ find . -maxdepth 2
.
./St?phane
./St?phane/Chazelas
./Stéphane
./Stéphane/Chazelas
./John
./John/Smith
$ find . \( ! -path './*/*' -o -prune \)
.
./St?phane
./St?phane/Chazelas
./St?phane/Chazelas/age
./St?phane/Chazelas/gender
./St?phane/Chazelas/address
./Stéphane
./Stéphane/Chazelas
./John
./John/Smith

Mon find(lorsque la sortie est envoyée à un terminal) affiche cet octet 0xe9 non valide comme ?ci-dessus. Vous pouvez voir que ce St<0xe9>phane/Chazelasn'était pas pruned.

Vous pouvez contourner ce problème en faisant:

LC_ALL=C find . \( ! -path './*/*' -o -prune \) -extra-conditions-and-actions

Mais notez que cela affecte tous les paramètres régionaux findet toutes les applications qu'il exécute (comme via les -execprédicats).

$ LC_ALL=C find . \( ! -path './*/*' -o -prune \)
.
./St?phane
./St?phane/Chazelas
./St??phane
./St??phane/Chazelas
./John
./John/Smith

Maintenant, je reçois vraiment un -maxdepth 2mais notez comment le é du second Stéphane correctement codé en UTF-8 est affiché comme ??les octets 0xc3 0xa9 (considérés comme deux caractères individuels non définis dans les paramètres régionaux C) du codage UTF-8 de é sont caractères non imprimables dans les paramètres régionaux C.

Et si j'avais ajouté un -name '????????', j'aurais eu le mauvais Stéphane (celui encodé en iso8859-1).

Pour appliquer à des chemins arbitraires au lieu de ., vous feriez:

find some/dir/. ! -name . -prune ...

pour -mindepth 1 -maxdepth 1ou:

find some/dir/. \( ! -path '*/./*/*' -o -prune \) ...

pour -maxdepth 2.

Je ferais quand même:

(cd -P -- "$dir" && find . ...)

D'abord parce que cela raccourcit les chemins, ce qui le rend moins susceptible de rencontrer des problèmes de chemin trop longs ou de liste d'arguments trop longs , mais aussi de contourner le fait qu'il findne peut pas prendre en charge les arguments de chemin arbitraires (sauf -favec FreeBSD find) car il s'étouffera valeurs $dirsimilaires !ou -print...


La -ocombinaison avec la négation est une astuce courante pour exécuter deux ensembles indépendants de -condition/ -actionin find.

Si vous souhaitez exécuter une -action1réunion de fichiers -condition1et indépendamment une -action2réunion de fichiers -condition2, vous ne pouvez pas:

find . -condition1 -action1 -condition2 -action2

Comme -action2ne serait exécuté que pour les fichiers répondant aux deux conditions.

Ni:

find . -contition1 -action1 -o -condition2 -action2

Comme -action2ne serait pas exécuté pour les fichiers qui remplissent les deux conditions.

find . \( ! -condition1 -o -action1 \) -condition2 -action2

fonctionne comme \( ! -condition1 -o -action1 \)résoudrait true pour chaque fichier. Cela suppose que -action1c'est une action (comme -prune, -exec ... {} +) qui retourne toujours vrai . Pour des actions comme -exec ... \;celle-ci peuvent retourner faux , vous voudrez peut-être ajouter un autre -o -something-somethingest inoffensif mais renvoie vrai comme -truedans GNUfind ou -links +0ou -name '*'(bien que notez le problème des caractères invalides ci-dessus).

Stéphane Chazelas
la source
1
Un jour, je rencontrerai un tas de fichiers chinois et je serai très heureux d'avoir lu vos nombreuses réponses sur les paramètres régionaux et les caractères valides. :)
Wildcard
2
@Wildcard, vous (et plus encore une personne chinoise) êtes plus susceptible de rencontrer des problèmes avec les noms de fichiers britanniques, français ... que les noms de fichiers chinois car les noms de fichiers chinois sont plus souvent encodés en UTF-8 que les noms de fichiers de scripts alphabétiques qui peut généralement être couvert par un jeu de caractères à un octet qui était la norme jusqu'à relativement récemment. Il existe d'autres jeux de caractères multi-octets pour couvrir le caractère chinois, mais je m'attendrais à ce que les Chinois soient passés à UTF-8 plus tôt que les occidentaux car ces jeux de caractères ont un certain nombre de problèmes désagréables. Voir aussi l'édition pour un exemple.
Stéphane Chazelas
0

J'ai rencontré un problème où j'avais besoin d'un moyen de limiter la profondeur lors de la recherche sur plusieurs chemins (au lieu de simplement .).

Par exemple:

$ find dir1 dir2 -name myfile -maxdepth 1

Cela m'a conduit à une approche alternative utilisant -regex. L'essentiel est:

-regex '(<list of paths | delimited>)/<filename>'

Donc, ce qui précède serait:

$ find dir1 dir2 -name myfile -regextype awk -regex '(dir1|dir2)/myfile' # GNU
$ find -E dir1 dir2 -name myfile -regex '(dir1|dir2)/myfile' # MacOS BSD

Sans nom de fichier:

$ find dir1 dir2 -name myfile -maxdepth 1 # GNU

-regex '(<list of paths | delimited>)/<anything that's not a slash>$'

$ find dir1 dir2 -name myfile -regextype awk -regex '(dir1|dir2)/[^/]*$' # GNU
$ find -E dir1 dir2 -name myfile -regex '(dir1|dir2)/[^/]*$' # MacOS BSD

Enfin, pour -maxdepth 2le regex, il devient:'(dir1|dir2)/([^/]*/){0,1}[^/]*$'

Alissa H
la source
1
Cette question demande cependant une solution standard (comme dans POSIX). Fonctionnerait également -maxdepthavec plusieurs chemins de recherche.
Kusalananda