Usando "find" para listar apenas diretórios sem mais filhos

Usando "find" para listar apenas diretórios sem mais filhos

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/CCCCeu gostaria de usar findapenas para listar /A/AA /A/AB/ABB /B /C/CC/CCC/CCCC.

O ponto de partida seria find . -type d, mas nem -mindepthnem -maxdepthpode ser usado, pode -noleafajudar (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 findpara 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 findlistar subdiretórios imediatamente após seu pai. O extra "/"é evitar a remoção espúria fooquando foobartambém existir. O deselegante NR!=1evita 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 subremove 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 zshexpnpá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; Nsignifica que a expansão está vazia se não houver correspondência; Dfaz 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_leaffunçã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.

informação relacionada