¿Cómo puedo hacer una búsqueda de amplitud utilizando `find`?

16

El -depthprimario a findhace que realice una búsqueda profunda primero.

Sin embargo, la secuencia predeterminada no es una búsqueda de amplitud.

La secuencia predeterminada podría describirse informalmente como un "recorrido de profundidad primero que maneja los nodos cuando se encuentran por primera vez en lugar de hacerlo durante el retroceso".

Tengo una necesidad real de amplitud primera búsqueda. ¿Cómo puedo hacer que se findcomporte de esta manera?


Por ejemplo, con la siguiente configuración:

$ mkdir -p alpha/{bravo,charlie,delta}
$ touch alpha/charlie/{alpha,beta,gamma,phi}

find tiene el siguiente comportamiento predeterminado:

$ find alpha
alpha
alpha/charlie
alpha/charlie/alpha
alpha/charlie/phi
alpha/charlie/beta
alpha/charlie/gamma
alpha/delta
alpha/bravo

y con -depth, se realiza de la siguiente manera:

$ find alpha -depth
alpha/charlie/alpha
alpha/charlie/phi
alpha/charlie/beta
alpha/charlie/gamma
alpha/charlie
alpha/delta
alpha/bravo
alpha

Sin embargo, lo que quiero es la siguiente opción (ficticia):

$ find alpha -bfs
alpha
alpha/charlie
alpha/delta
alpha/bravo
alpha/charlie/alpha
alpha/charlie/phi
alpha/charlie/beta
alpha/charlie/gamma

En otras palabras, necesito findprocesar / informar sobre todos los archivos / directorios a una profundidad determinada antes de continuar.

¿Cómo puedo hacer esto?

Comodín
fuente
No con find(al menos, no solo con find). ¿Desea solo enumerar los archivos o desea utilizar otras primarias?
Gilles 'SO- deja de ser malvado'
@Gilles, en realidad me di cuenta de que -bfsno sería lo que necesito ... Tengo un script simple que genera un índice para un gran proyecto de GitLab, adecuado para su inclusión en el Wiki de GitLab. Hace que los encabezados se basen jerárquicamente en los nombres de directorio. Funciona muy bien, excepto que en la estructura de archivos de ejemplo anterior se colocaría deltadebajo del charliesubtítulo, en lugar de debajo del alphaencabezado principal .
Comodín
Otra cosa extraña es que mi findsalida está ordenada alfabéticamente. No tengo idea de por qué ....
Wildcard
Aún así, creo que -bfs podría ser útil, incluso si no se ajusta perfectamente a este caso de uso.
Comodín
2
Implementé dicha herramienta: bfs . Todavía no es 100% compatible con GNU find, pero está llegando allí.
Tavian Barnes

Respuestas:

6

Puede hacerlo solo con comodines de shell. Construya un patrón con progresivamente más niveles de directorio.

pattern='*'
set -- $pattern
while [ $# -ne 1 ] || [ "$1" != "$pattern" ]; do
  for file; do
    …
  done
  pattern="$pattern/*"
  set -- $pattern
done

Esto pierde archivos de puntos. Use FIGNORE='.?(.)'en ksh, shopt -s dotgloben bash o setopt glob_dotsen zsh para incluirlos.

Advertencias:

  • Esto explotará la memoria si hay muchos archivos.
  • Esto atraviesa enlaces simbólicos a directorios de forma recursiva.

Si desea elegir el orden o directorios y no directorios, y el rendimiento no es crítico, puede hacer dos pases y probar [ -d "$file" ]en cada pase.

Gilles 'SO- deja de ser malvado'
fuente
@Wildcard Sí, lo hice.
Gilles 'SO- deja de ser malvado'
1
¡Agradable! Una advertencia más casi trivial: fallará al procesar un archivo que es el archivo solitario en un directorio si el archivo se nombra literalmente *. :)
Comodín
@Wildcard Oh, sí, olvidé mencionar eso. Use bash o zsh con nullgloby use (($#))como condición de bucle para evitar este caso límite.
Gilles 'SO- deja de ser malvado'
5

# cat ./bfind

#!/bin/bash
i=0
while results=$(find "$@" -mindepth $i -maxdepth $i) && [[ -n $results ]]; do
  echo "$results"
  ((i++))
done

Esto funciona aumentando la profundidad findy repitiendo, creo que puede repetir resultados, pero podría filtrarse fácilmente

usuario239175
fuente
Lo siento, no sabía sobre el mecanismo de formato. De todos modos, en realidad, no se repite, porque creo que se corta con nada menos que mindepth
user239175
3

Puede canalizarlo finden un orden que se clasifica principalmente por el número de /caracteres en el nombre de ruta. Por ejemplo,

find alpha |
awk '{n=gsub("/","/",$0);printf "%04d/%s\n",n,$0}' |
sort -t/ |
sed 's|[^/]*/||'

Esto se usa awkpara prefijar el nombre de ruta con el número de barras y sedpara eliminar este prefijo al final.

En realidad, como probablemente desee alpha/charlie+que se enumeren los contenidos del directorio alpha/charlie, debe decir sort -t/ -k1,1 -k2,2 -k3,3 -k4,4hasta la profundidad deseada.

meuh
fuente
0

Otra respuesta no basada en 'find' sino en bash: use primero la "longitud del directorio padre", luego ordene por alfa.

La respuesta no coincide ya que sus resultados tienen "Charlie, Bravo, Delta", pero me preguntaba si debería ser "Bravo, Charlie, Delta" en orden alfa.

paths_breadth_first() {
  while IFS= read -r line; do
    dirn=${line%/*}         ## dirname(line)
    echo ${#dirn},$line     ## len(dirn),line
  done | sort -n | cut -d ',' -f 2-
}

Eso produce

  $ cat /tmp/yy | paths_breadth_first 
  alpha
  alpha/bravo
  alpha/charlie
  alpha/delta
  alpha/charlie/alpha
  alpha/charlie/beta
  alpha/charlie/gamma
  alpha/charlie/phi
qneill
fuente