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:) }