Utilizando “find” para listar apenas diretórios sem mais childs

4

Como podem apenas ser listados diretórios, que não têm 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 Gostaria de usar find para listar apenas /A/AA /A/AB/ABB /B /C/CC/CCC/CCCC .

O ponto de partida seria find . -type d , mas nem -mindepth nem -maxdepth podem ser usados, pode -noleaf ajudar (não consegui fazer com que reagisse como eu queria)?

    
por MaoPU 05.10.2010 / 02:09

2 respostas

4

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:) }
    
por 06.10.2010 / 00:51
1

Isso é o que eu uso:

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

Chame-o usando o diretório para começar:

leaf /start/dir
    
por 05.10.2010 / 03:18