¿Limitar la búsqueda POSIX a una profundidad específica?

15

Hace poco noté que las especificaciones POSIX parafind no incluyen el -maxdepthprimario.

Para aquellos que no están familiarizados con él, el objetivo de la -maxdepthprimaria es restringir cuántos niveles de profundidad finddescenderán. -maxdepth 0da como resultado que solo se procesen los argumentos de la línea de comandos; -maxdepth 1solo manejaría los resultados directamente dentro de los argumentos de la línea de comando, etc.

¿Cómo puedo obtener el comportamiento equivalente al -maxdepthprimario no POSIX usando solo las opciones y herramientas especificadas por POSIX?

(Nota: Por supuesto, puedo obtener el equivalente de -maxdepth 0solo usarlo -prunecomo primer operando, pero eso no se extiende a otras profundidades).

Comodín
fuente
@StevenPenny, FreeBSD's -depth -2, -depth 1... el enfoque podría ser visto como mejor que el de GNU -maxdepth/-mindepth
Stéphane Chazelas
@ StéphaneChazelas de cualquier manera: POSIX find debe tener uno u otro; de lo contrario está lisiado
Steven Penny
1
Al menos para -maxdepth/ -mindepth, hay alternativas razonables (tenga en cuenta que -pathes una adición reciente a POSIX). Las alternativas para -timexyo -mtime -3m(o -mmin -3) son mucho más engorrosas. A algunos les gusta -execdir/ -deleteno tienen una alternativa confiable.
Stéphane Chazelas
2
@StevenPenny, siéntase libre de registrar un ticket en austingroupbugs.net para solicitar que se agregue. He visto que se agregan cosas sin la necesidad de un patrocinador cuando había una justificación sólida. Un curso de acción probablemente mejor sería conseguir que tantas implementaciones lo agreguen primero para que POSIX solo tenga que especificar el existente, que generalmente es menos polémico.
Stéphane Chazelas
@ StéphaneChazelas en mi caso terminé simplemente nombrando los archivos directamente, pero gracias; Podría presentar una multa si esto vuelve a aparecer
Steven Penny

Respuestas:

7

Puede usar -pathpara hacer coincidir una profundidad dada y podar allí. P.ej

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

sería maxdepth 1, ya que *coincide con ., */*coincide ./dir1y */*/*coincide ./dir1/dir2que se poda. Si usa un directorio de inicio absoluto, también debe agregar un líder /al -path.

meuh
fuente
Hmmm, complicado. ¿No podría simplemente eliminar una capa del /*final del patrón, eliminar el -ooperador y obtener el mismo resultado?
Comodín el
No, porque *coincide /también, por lo que el directorio a/b/c/d/eencajaría -path */*, lamentablemente.
meuh
Pero a/b/c/d/enunca se alcanzaría , porque -prunese aplicaría a a/b...
Wildcard
1
Lo siento, leí mal -pruney me -oeliminaron. Si mantiene el -pruneproblema, el problema es que */*no coincidirá con nada en un nivel superior a la profundidad máxima, por ejemplo, el directorio único a.
meuh
11

El enfoque de @meuh es ineficiente ya que su -maxdepth 1enfoque todavía permite findleer el contenido de los directorios en el nivel 1 para luego ignorarlos de lo contrario. Tampoco funcionará correctamente con algunas findimplementaciones (incluida GNU find) si algunos nombres de directorio contienen secuencias de bytes que no forman caracteres válidos en la configuración regional del usuario (como para los nombres de archivo en una codificación de caracteres diferente).

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

es la forma más canónica de implementar GNU -maxdepth 1(o FreeBSD -depth -2).

En general, es lo -depth 1que quieres ( -mindepth 1 -maxdepth 1), ya que no quieres considerarlo .(profundidad 0), y luego es aún más simple:

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

Porque -maxdepth 2eso se convierte en:

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

Y ahí es donde se ejecutan los problemas de caracteres no válidos.

Por ejemplo, si tiene un directorio llamado Stéphanepero que éestá codificado en el conjunto de caracteres iso8859-1 (también conocido como latin1) (0xe9 byte) como era más común en Europa occidental y América hasta mediados de la década de 2000, entonces ese byte 0xe9 no es un Carácter válido en UTF-8. Por lo tanto, en las configuraciones regionales UTF-8, el *comodín (con algunas findimplementaciones) no coincidirá, Stéphaneya que *es 0 o más caracteres y 0xe9 no es un carácter.

$ 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

Mi find(cuando la salida va a una terminal) muestra ese byte 0xe9 inválido como el ?anterior. Puedes ver que St<0xe9>phane/Chazelasno fue pruned.

Puede solucionarlo haciendo:

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

Pero tenga en cuenta que eso afecta a toda la configuración regional findy a cualquier aplicación que ejecute (como a través de los -execpredicados).

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

Ahora, realmente obtengo un mensaje, -maxdepth 2pero tenga en cuenta cómo el é en el segundo Stéphane codificado correctamente en UTF-8 se muestra ??como los 0xc3 0xa9 bytes (considerados como dos caracteres indefinidos individuales en el entorno C) de la codificación UTF-8 de é caracteres no imprimibles en la configuración regional C.

Y si hubiera agregado un -name '????????', habría obtenido el Stéphane incorrecto (el codificado en iso8859-1).

Para aplicar a rutas arbitrarias en lugar de hacerlo ., haría lo siguiente:

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

para -mindepth 1 -maxdepth 1o:

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

para -maxdepth 2.

Todavía haría un:

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

Primero porque eso hace que las rutas sean más cortas, lo que hace que sea menos probable que se encuentre con una ruta demasiado larga o que contengafind argumentos demasiado largos , pero también que evite el hecho de que no puede admitir argumentos de ruta arbitraria (excepto -fcon FreeBSD find) ya que se ahogará valores de me $dirgusta !o -print...


En -ocombinación con la negación es un truco común para ejecutar dos conjuntos independientes de -condition/ -actionin find.

Si desea ejecutar -action1en reunión de archivos -condition1e independientemente -action2en reunión de archivos -condition2, no puede hacer:

find . -condition1 -action1 -condition2 -action2

Como -action2solo se ejecutaría para archivos que cumplan ambas condiciones.

Ni:

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

Como -action2no se ejecutaría para archivos que cumplan ambas condiciones.

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

funciona como \( ! -condition1 -o -action1 \)se resolvería a verdadero para cada archivo. Eso supone que -action1es una acción (como -prune, -exec ... {} +) que siempre devuelve verdadero . Por acciones como -exec ... \;que pueden volver falsa , es posible que desee agregar otro -o -somethingdonde -somethinges inofensivo pero devuelve cierto como -trueen GNU findo -links +0o -name '*'(aunque tenga en cuenta la cuestión acerca de caracteres no válidos anteriores).

Stéphane Chazelas
fuente
1
Algún día me encontraré con un montón de archivos chinos y me alegrará mucho haber leído sus muchas respuestas sobre la configuración regional y los caracteres válidos. :)
Comodín el
2
@Wildcard, es más probable que usted (y aún más una persona china) tenga problemas con los nombres de archivos británicos, franceses que con los nombres de archivos chinos, ya que los nombres de archivos chinos se codifican con mayor frecuencia en UTF-8 que los nombres de archivos de secuencias de comandos alfabéticas eso generalmente puede estar cubierto por un conjunto de caracteres de un solo byte que era la norma hasta hace relativamente poco. Hay otros conjuntos de caracteres de varios bytes para cubrir el carácter chino, pero espero que los chinos se hayan cambiado a UTF-8 antes que los occidentales, ya que esos conjuntos tienen varios problemas desagradables. Vea también la edición para un ejemplo.
Stéphane Chazelas
0

Me encontré con un problema en el que necesitaba una forma de limitar la profundidad al buscar múltiples rutas (en lugar de solo .).

Por ejemplo:

$ find dir1 dir2 -name myfile -maxdepth 1

Esto me llevó a un enfoque alternativo usando -regex. La esencia es:

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

Entonces, lo anterior sería:

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

Sin un nombre de archivo:

$ 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

Finalmente, para -maxdepth 2la expresión regular cambia a:'(dir1|dir2)/([^/]*/){0,1}[^/]*$'

Alissa H
fuente
1
Sin embargo, esta pregunta solicita una solución estándar (como en POSIX). También -maxdepthfuncionaría con múltiples rutas de búsqueda.
Kusalananda