¿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/CCCC
me gustaría usar find
solo para enumerar /A/AA /A/AB/ABB /B /C/CC/CCC/CCCC
.
El punto de partida sería find . -type d
, pero ni -mindepth
ni -maxdepth
se puede usar, ¿puede -noleaf
ayudar (no pude lograr que reaccionara como quería)?
Respuesta1
Aquí hay una solución compatible con POSIX que posprocesa la salida find
para 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 find
enumera los subdirectorios inmediatamente después de su padre. El extra "/"
es evitar eliminar espuriamente foo
cuando foobar
también existe. Lo poco elegante NR!=1
evita 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 sub
elimina 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 zshexpn
pá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; N
significa que la expansión está vacía si no hay coincidencias; D
hace que se incluyan archivos dot; :h
es 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_leaf
funció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.