Exclui todos os arquivos, exceto em um determinado subdiretório com find

7

Desejo excluir recursivamente todos os arquivos não acessados em algum momento na pasta a , exceto todos os arquivos da subpasta b .

find a \( -name b -prune \) -o -type f -delete

No entanto, recebo uma mensagem de erro:

find: The -delete action automatically turns on -depth, but -prune does nothing when -depth is in effect. If you want to carry on anyway, just explicitly use the -depth option.

Adicionar -depth faz com que todos os arquivos em b sejam incluídos, o que deve não acontecer.

Alguém sabe uma maneira segura de fazer isso funcionar?

    
por forthrin 18.08.2013 / 19:01

2 respostas

9

TL; DR: a melhor maneira é usar -exec rm em vez de -delete .

find a \( -name b -prune \) -o -type f -exec rm {} +

Explicação:

Por que encontrar reclamações quando você tenta usar -delete com -prune ?

Resposta curta: porque -delete implica em -depth e -depth torna -prune ineficaz.

Antes de chegarmos à resposta longa, primeiro observe o comportamento de encontrar com e sem -depth :

$ find foo/
foo/
foo/f1
foo/bar
foo/bar/b2
foo/bar/b1
foo/f2

Não há garantia sobre o pedido em um único diretório. Mas há uma garantia de que um diretório é processado antes de seu conteúdo. Anote foo/ antes de qualquer foo/* e foo/bar antes de qualquer foo/bar/* .

Isso pode ser revertido com -depth .

$ find foo/ -depth
foo/f2
foo/bar/b2
foo/bar/b1
foo/bar
foo/f1
foo/

Observe que agora todos os foo/* aparecem antes de foo/ . Mesmo com foo/bar .

Resposta mais longa:

  • -prune impede que a localização desça para um diretório. Em outras palavras, -prune ignora o conteúdo do diretório. No seu caso, -name b -prune evita que o encontro descesse em qualquer diretório com o nome b .
  • -depth faz find para processar o conteúdo de um diretório antes do próprio diretório. Isso significa que, no momento em que find, é possível processar a entrada de diretório b , seu conteúdo já foi processado. Assim, -prune é ineficaz com -depth em vigor.
  • -delete implica -depth , portanto, ele pode excluir os arquivos primeiro e depois o diretório vazio. -delete se recusa a excluir diretórios não vazios. Eu acho que seria possível adicionar uma opção para forçar -delete a excluir diretórios não vazios e / ou evitar que -delete indicasse -depth . Mas isso é outra história.

Existe outra maneira de conseguir o que você deseja:

find a -not -path "*/b*" -type f -delete

Isso pode ou não ser mais fácil de lembrar.

Este comando ainda desce para o diretório b e processa cada arquivo nele apenas para -not rejeitá-los. Isso pode ser um problema de desempenho se o diretório b for enorme.

-path funciona de forma diferente de -name . -name corresponde apenas ao nome (do arquivo ou diretório) enquanto -path está correspondendo no caminho inteiro. Por exemplo, observe o caminho /home/lesmana/foo/bar . -name -bar corresponderá porque o nome é bar . -path "*/foo*" corresponderá porque a string /foo está no caminho. -path tem algumas complexidades que você deve entender antes de usá-lo. Leia a página do manual de find para mais detalhes.

Tenha em atenção que isto não é 100% infalível. Há chances de "falsos positivos". A maneira como o comando é escrito acima, ignorará qualquer arquivo que tenha qualquer diretório pai cujo nome esteja iniciando com b (positivo). Mas também irá ignorar qualquer arquivo cujo nome esteja iniciando com b , independentemente da posição na árvore (falso positivo). Isso pode ser corrigido escrevendo uma expressão melhor que "*/b*" . Isso é deixado como um exercício para o leitor.

Suponho que você usou a e b como espaços reservados e os nomes reais são mais como allosaurus e brachiosaurus . Se você colocar brachiosaurus no lugar de b , a quantidade de falsos positivos será drasticamente reduzida.

Pelo menos os falsos positivos serão não excluídos, por isso não será tão trágico. Além disso, você pode verificar os falsos positivos primeiro executando o comando sem -delete (mas lembre-se de colocar o -depth implícito) e examinar a saída.

find a -not -path "*/b*" -type f -depth
    
por 18.08.2013 / 19:29
3

Use apenas rm em vez de -delete :

find a -name b -prune -o -type f -exec rm -f {} +
    
por 18.08.2013 / 21:34