Como podem ser listados apenas diretórios que não possuem outro diretório filho?
Imagine uma estrutura como /A /A/AA /A/AB /A/AB/ABB /B /C /C/CC /C/CC/CCC /C/CC/CCC/CCCC
eu gostaria de usar find
apenas para listar /A/AA /A/AB/ABB /B /C/CC/CCC/CCCC
.
O ponto de partida seria find . -type d
, mas nem -mindepth
nem -maxdepth
pode ser usado, pode -noleaf
ajudar (não consegui fazer com que reagisse da maneira que queria)?
Responder1
Aqui está uma solução compatível com POSIX que pós-processa a saída find
para remover diretórios que possuem um subdiretório listado. Ele pressupõe que não há novas linhas nos nomes dos diretórios.
{ find . -type d; echo; } |
awk 'index($0,prev"/")!=1 && NR!=1 {print prev}
1 {sub(/\/$/,""); prev=$0}'
Explicação: o script awk atrasa a impressão de cada linha até ler a próxima linha e só imprime a linha anterior se não for um prefixo. Isso aproveita o fato de find
listar subdiretórios imediatamente após seu pai. O extra "/"
é evitar a remoção espúria foo
quando foobar
também existir. O deselegante NR!=1
evita imprimir uma linha inicial vazia, e o deselegante echo;
não deve ter um caso especial igualmente deselegante para a última linha. A chamada para sub
remove uma barra final do diretório de nível superior, caso eg find ./
tenha sido chamado.
Como de costume, há uma linha zsh enigmática.
echo **/.(e\''test -z $REPLY/*(/DN[1])'\':h)
Versão mais longa e legível:
is_leaf () { [ -z $REPLY/*(/DN[1]) ] }
echo **/.(+is_leaf:h)
A última linha pode ser simplificada echo **/(+is_leaf)
se você não se importar com o final /
.
Explicação resumida: O que está entre parênteses éeliminatórias globais, documentado na zshexpn
página de manual. Filtramos os resultados do glob **/
(expandindo para o diretório atual e todos os seus subdiretórios), mantendo apenas aqueles para os quais a função is_leaf
(ou o código entre '…'
) retorna 0. O código do filtro globiza os subdiretórios da correspondência que está sendo testada ( $REPLY
) (em na verdade, [1]
faz com que ele pare após o primeiro subdiretório) e retorna um status indicando se pelo menos um subdiretório foi encontrado. O qualificador glob /
restringe a expansão a diretórios; N
significa que a expansão está vazia se não houver correspondência; D
faz com que arquivos de ponto sejam incluídos; :h
é um modificador de histórico e faz com que o /.
sufixo seja removido (em geral significa dirname
).
Apenas para ilustrar as possibilidades dos qualificadores glob do zsh, aqui estão duas outras variantes (mais longas e acho mais obscuras) com uma is_leaf
função correspondente:
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:) }
Responder2
Isto é o que eu uso:
leaf () { find "${1:-.}" -depth -type d | sed 'h; :b; $b; N; /^\(.*\)\/.*\n\1$/ { g; bb }; $ {x; b}; P; D'; }
Chame-o usando o diretório para começar:
leaf /start/dir
Responder3
Para encontrar diretórios que não possuem subdiretórios:
find dir -type d -links 2
explicação: um diretório possui um link para cada subdiretório, um link de seu pai e um link para si mesmo, portanto, uma contagem de 2 links se não tiver subdiretórios.