Usar "buscar" para enumerar solo directorios sin más hijos

Usar "buscar" para enumerar solo directorios sin más hijos

¿Cómo se pueden enumerar solo directorios que no tienen otro directorio secundario?

Imagine una estructura como la que /A /A/AA /A/AB /A/AB/ABB /B /C /C/CC /C/CC/CCC /C/CC/CCC/CCCCme gustaría usar findsolo para enumerar /A/AA /A/AB/ABB /B /C/CC/CCC/CCCC.

El punto de partida sería find . -type d, pero ni -mindepthni -maxdepthse puede usar, ¿puede -noleafayudar (no pude lograr que reaccionara como quería)?

Respuesta1

Aquí hay una solución compatible con POSIX que posprocesa la salida findpara eliminar directorios que tienen un subdirectorio listado. Se supone que no hay nuevas líneas en los nombres de los directorios.

{ find . -type d; echo; } |
awk 'index($0,prev"/")!=1 && NR!=1 {print prev}
     1 {sub(/\/$/,""); prev=$0}'

Explicación: el script awk retrasa la impresión de cada línea hasta que se lee la línea siguiente y solo imprime la línea anterior si no es un prefijo. Esto aprovecha el hecho de que findenumera los subdirectorios inmediatamente después de su padre. El extra "/"es evitar eliminar espuriamente foocuando foobartambién existe. Lo poco elegante NR!=1evita imprimir una línea inicial vacía, y lo poco elegante echo;no debe tener un caso especial igualmente poco elegante para la última línea. La llamada a subelimina una barra diagonal final del directorio de nivel superior, en caso de que, por ejemplo, find ./se haya llamado.


Como de costumbre, hay una frase críptica sobre zsh.

echo **/.(e\''test -z $REPLY/*(/DN[1])'\':h)

Versión más larga y legible:

is_leaf () { [ -z $REPLY/*(/DN[1]) ] }
echo **/.(+is_leaf:h)

La última línea se puede simplificar a echo **/(+is_leaf)si no le importa el final /.

Explicación resumida: lo que está entre paréntesis esclasificatorios globales, documentado en la zshexpnpágina de manual. Filtramos los resultados del glob **/(expandiéndonos al directorio actual y todos sus subdirectorios), manteniendo solo aquellos para los cuales la función is_leaf(o el código entre '…') devuelve 0. El código de filtro globs los subdirectorios de la coincidencia que se está probando ( $REPLY) (en De hecho, [1]hace que se detenga después del primer subdirectorio) y devuelve un estado que indica si se encontró al menos un subdirectorio. El calificador global /restringe la expansión a directorios; Nsignifica que la expansión está vacía si no hay coincidencias; Dhace que se incluyan archivos dot; :hes un modificador de historial y hace que /.se elimine el sufijo (en general significa dirname).

Sólo para ilustrar las posibilidades de los calificadores globales de zsh, aquí hay otras dos variantes (más largas y creo que más oscuras) con una is_leaffunción correspondiente:

echo **/.(e\''tmp=($REPLY/*(/DN[1])); ((!#tmp))'\':h)
echo **/.(e\''$REPLY/*(/DN[1]e:REPLY=false:)'\':h)
is_leaf () { set -- $REPLY/*(/DN[1]); ((!#)); }
is_leaf () { return $REPLY/*(/DN[1]e:REPLY=1:) }

Respuesta2

Esto es lo que uso:

leaf () { find "${1:-.}" -depth -type d | sed  'h; :b; $b; N; /^\(.*\)\/.*\n\1$/ { g; bb }; $ {x; b}; P; D'; }

Llámelo usando el directorio para comenzar desde:

leaf /start/dir

Respuesta3

Para buscar directorios que no tengan subdirectorios:

find dir -type d -links 2

Explicación: un directorio tiene un enlace para cada subdirectorio que contiene, un enlace de su padre y un enlace a sí mismo, por lo tanto, cuenta 2 enlaces si no tiene subdirectorios.

información relacionada