Aqui está uma solução compatível com POSIX que pós-processa a saída de find
para remover diretórios que possuem um subdiretório listado. Ele assume que não há novas linhas nos nomes de diretório.
{ 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 que find
lista os subdiretórios imediatamente após o pai. O "/"
extra é para evitar a remoção espúria de foo
quando foobar
também existir. O deselegante NR!=1
evita imprimir uma linha vazia inicial, 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, no caso, por exemplo find ./
foi chamado.
Como de costume, há um one-liner zsh enigmático.
echo **/.(e\''test -z $REPLY/*(/DN[1])'\':h)
Versão mais longa e mais legível:
is_leaf () { [ -z $REPLY/*(/DN[1]) ] }
echo **/.(+is_leaf:h)
A última linha pode ser simplificada para echo **/(+is_leaf)
se você não se importa com o /
.
Explicação do resumo: As coisas entre parênteses são qualifiers , documentados na página zshexpn
man. 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 de filtro globs os subdiretórios da correspondência sendo testada ( $REPLY
) (na verdade, [1]
faz com que pare após o primeiro subdiretório) e retorna um status indicando se pelo menos um subdiretório foi encontrado. O qualificador de glob /
restringe a expansão aos diretórios; N
significa que a expansão está vazia se não houver correspondência; D
faz com que arquivos de pontos 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 mais obscuras) com uma função is_leaf
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:) }