Como remover todos os diretórios vazios em uma subárvore?

151

Como posso remover todos os diretórios vazios em uma subárvore? Eu usei algo como

find . -type d -exec rmdir {} 2>/dev/null \;

mas preciso executar várias vezes para remover diretórios que contêm apenas diretórios vazios. Além disso, é bastante lento, especialmente no cygwin.

maaartinus
fonte
Consulte também emacs.stackexchange.com/q/12190/2264 para obter uma solução emacs.
Sean Allred

Respostas:

222

Combinando findopções e predicados GNU , este comando deve fazer o trabalho:

find . -type d -empty -delete
  • -type d restringe a diretórios
  • -empty restringe aos vazios
  • -delete remove cada diretório

A árvore é retirada das folhas sem a necessidade de especificar -depthcomo está implícita -delete.

Christophe Drevet-Droguet
fonte
2
-deletejá implica -depthpara que você não precise especificar isso manualmente.
Jamadagni
1
Obrigado, eu não percebi isso. Resposta atualizada.
Christophe Drevet-Droguet
11
Eu acrescentaria -mindepth 1aqui, para impedir a exclusão do próprio diretório inicial, se ele estiver vazio.
Greg Dubicki
2
Grande, mas não funciona em meus velhos anfitriões SunOS ...
dokaspar
2
!tem um significado especial para o shell. Você precisa escapar disso. Algo como: \! -name 'Completed'logo antes -deletedeve funcionar. Ou você apenas coloca um arquivo de marcador neste diretório.
Christophe Drevet-Droguet
53

Liste os diretórios profundamente aninhados primeiro.

find . -depth -type d -exec rmdir {} \; 2>/dev/null

(Observe que o redirecionamento se aplica ao findcomando como um todo, não apenas ao rmdir. Redirecionar apenas para rmdircausaria uma desaceleração significativa, pois você precisaria chamar um shell intermediário.)

Você pode evitar a execução rmdirem diretórios não vazios, passando o -emptypredicado para localizar. O GNU find testa o diretório quando está prestes a executar o comando, para que os diretórios que acabaram de ser esvaziados sejam escolhidos.

find . -depth -type d -empty -exec rmdir {} \;

Outra maneira de acelerar seria agrupar as rmdirinvocações. É provável que ambos sejam visivelmente mais rápidos que o original, especialmente no Cygwin. Não espero muita diferença entre esses dois.

find . -depth -type d -print0 | xargs -0 rmdir 2>/dev/null
find . -depth -type d -exec rmdir {} + 2>/dev/null

Qual método é mais rápido depende de quantos diretórios não vazios você possui. Você não pode combinar -emptycom métodos para agrupar chamadas, porque os diretórios que contêm apenas diretórios vazios não ficam vazios quando os findolha.

Outro método seria executar várias passagens. Se isso é mais rápido depende de muitas coisas, incluindo se toda a hierarquia de diretórios pode permanecer no cache do disco entre as findexecuções.

while [ -n "$(find . -depth -type d -empty -print -exec rmdir {} +)" ]; do :; done

Como alternativa, use zsh. O qualificador glob F corresponde a diretórios não vazios, portanto, /^Fcorresponde a diretórios vazios. Diretórios que contêm apenas diretórios vazios não podem ser correspondidos tão facilmente.

while rmdir **/*(/N^F); do :; done

(Isso termina quando rmdirrecebe uma linha de comando vazia.)

Gilles
fonte
É isso aí. Em vez de 90 segundos, são necessários 0,90 s.
Maaartinus 01/03
@maaartinus: Estou curioso: você tem um conjunto de dados semelhante onde poderia tentar -p? Eu não teria pensado que isso faria diferença.
Gilles
3
@ maartinus - outras pequenas otimizações: a adição -emptydeve funcionar com esta (embora eu não tenha certeza exatamente quanto ela ganhará). E muito, muito trivialmente, já que você provavelmente não deseja remover ., use -mindepth 1.
mattdm
Não foi a remoção, mas o processo foi iniciado, o que levou quase o tempo todo. Eu havia ignorado o -depthargumento, o que torna rmdir -pinútil. Eu já mudei meu comentário. Os anos 90 foram minha tentativa original; não há nada de surpreendente aqui.
Maaartinus 01/03
2
Percebi podemos remover a rmdirchamada de comando por completo, pelo menos com GNU encontrar, com este comando:find . -depth -type d -empty -delete
Christophe Drevet-Droguet
6

Se você apenas colocar um -pno seu rmdir, isso funcionará de uma só vez. Não será bonito ou ideal, mas deve ter tudo. Isso diz ao rmdir para remover qualquer diretório-pai não vazio daquele que você está removendo.

Você pode economizar um pouco adicionando o -emptyteste a encontrar, para que ele não se incomode com diretórios não vazios.

mattdm
fonte
3

find . -depth -type d -exec rmdir {} +

é a resposta mais simples e compatível com esta pergunta.

Infelizmente, as outras respostas fornecidas aqui dependem de aprimoramentos específicos do fornecedor que não existem em todos os sistemas.

esperto
fonte
3
Esta resposta gera um erro para cada diretório que não pode ser excluído, o que pode ser menor que o desejável.
Willem van Ketwich 13/04/19
0

find . -type d -printf "%d %p\n" |\ sort -nr |\ perl -pe 's/^\d+\s//;' |\ while read dir; do \ (rmdir "$dir" > /dev/null 2>&1); \ done

Veja como funciona:

  1. Liste recursivamente todos os diretórios, juntamente com sua profundidade
  2. Classificar por ordem decrescente da profundidade
  3. Filtrar apenas os caminhos do diretório
  4. Executar rmdirna lista, um por um
Ashish Ranjan
fonte
0

Eu uso esses aliases para findcomandos usados ​​com freqüência , especialmente quando eu limpo o espaço em disco usando o dupeguru , onde remover duplicatas pode resultar em muitos diretórios vazios.

Comentários lá dentro, .bashrcpara não esquecê-los mais tarde, quando precisar ajustá-los.

# find empty directories
alias find-empty='find . -type d -empty'

# fine empty/zero sized files
alias find-zero='find . -type f -empty'

# delete all empty directories!
alias find-empty-delete='find-empty -delete'

# delete empty directories when `-delete` option is not available.
# output null character (instead of newline) as separator. used together
# with `xargs -0`, will handle filenames with spaces and special chars.
alias find-empty-delete2='find-empty -print0 | xargs -0 rmdir -p'

# alternative version using `-exec` with `+`, similar to xargs.
# {}: path of current file
# +: {} is replaced with as many pathnames as possible for each invocation.
alias find-empty-delete3='find-empty -exec rmdir -p {} +'

# for removing zero sized files, we can't de-dupe them automatically
# since they are technically all the same, so they are typically left
# beind. this removes them if needed.
alias  find-zero-delete='find-zero -delete'
alias find-zero-delete2='find-zero -print0 | xargs -0 rm'
alias find-zero-delete3='find-zero -exec rm {} +'
raychi
fonte
-2

rm -r */comando funcionou facilmente para mim. rmdeve exigir -fa remoção forçada de diretórios com arquivos. rm -rdeve remover apenas diretórios vazios. Estou aberto a entender por que isso pode estar errado. Isso também deve deixar os arquivos, pois */apenas analisa as pastas.

libroman2
fonte
1
Eu recomendo fortemente testá-lo completamente primeiro, pois rmse destina principalmente a remover arquivos. Embora */corresponda apenas aos diretórios, não tenho idéia do que ele faz em níveis mais profundos. Também posso imaginar que ele funciona apenas em alguns sistemas.
maaartinus 22/06