Exclua todos os arquivos, exceto em um determinado subdiretório, com a localização

11

Desejo excluir recursivamente todos os arquivos que não foram acessados ​​por um tempo na pasta a, exceto todos os arquivos na subpasta b.

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

No entanto, recebo uma mensagem de erro:

find: A ação -delete ativa automaticamente a profundidade, mas -prune não faz nada quando a profundidade está em vigor. Se você quiser continuar de qualquer maneira, use explicitamente a opção -thpth.

A adição -depthfaz com que todos os arquivos bsejam incluídos, o que não deve acontecer.

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

fortrina
fonte
@ MichaelKjörling: Eu dei uma olhada no extglob, mas como você inclui tudo amenos a/b?
forthrin
Não vai cd a && ls -d !(b/*)funcionar? (Para fazê-lo, apenas rm -rem vez de ls -d.)
um CVn
Sua sugestão exclui subpastas. Quero manter as pastas intactas. Quero encontrar e excluir todos os arquivos na árvore em a(exceto arquivos em a/b).
forthrin 18/08/13
Então, basta pular -rpara a rm. Parece que o que você está perguntando é facilmente respondido usando o globbing estendido do bash, e então o que você faz com o resultado do globbing é com você.
um CVn
@ MichaelKjörling Só porque os dois problemas têm soluções que se assemelham vagamente, as perguntas não são duplicadas. A maioria das soluções para cada um dos dois problemas não resolve o outro problema.
Gilles 'SO- stop be evil'

Respostas:

13

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

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

Explicação:

Por que encontrar reclamar quando você tentar usar -deletecom -prune?

Resposta curta: porque -deleteimplica -depthe -depthtorna -pruneineficaz.

Antes de chegarmos à resposta longa, observe primeiro 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 seja processado antes de seu conteúdo. Anote foo/antes de qualquer foo/*e foo/barantes 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 foo/*aparecem antes foo/. O mesmo com foo/bar.

Resposta mais longa:

  • -pruneimpede que a localização desça para um diretório. Em outras palavras, -prunepula o conteúdo do diretório. No seu caso, -name b -pruneimpede que a localização desça para qualquer diretório com o nome b.
  • -depthfaz encontrar para processar o conteúdo de um diretório antes do próprio diretório. Isso significa que no momento em que a find processa a entrada do diretório, bseu conteúdo já foi processado. Assim -pruneé ineficaz com -depthefeito.
  • -deleteimplica -depthque ele possa excluir os arquivos primeiro e depois o diretório vazio. -deletese recusa a excluir diretórios não vazios. Eu acho que seria possível adicionar uma opção para forçar -deletea exclusão de diretórios não vazios e / ou impedir -deletea implicação -depth. Mas isso é outra história.

Há 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 be processa todos os arquivos nele apenas para -notrejeitá-los. Isso pode ser um problema de desempenho se o diretório bfor enorme.

-pathfunciona de forma diferente do que -name. -namecorresponde apenas ao nome (do arquivo ou diretório) enquanto -pathcorresponde ao caminho inteiro. Por exemplo, observe o caminho /home/lesmana/foo/bar. -name -barcorresponderá porque o nome é bar. -path "*/foo*"corresponderá porque a cadeia /fooestá no caminho. -pathtem alguns meandros que você deve entender antes de usá-lo. Leia a página do manual findpara mais detalhes.

Cuidado que isso não é 100% infalível. Há chances de "falsos positivos". A maneira como o comando é escrito acima irá pular qualquer arquivo que possua qualquer diretório pai cujo nome esteja começando com b(positivo). Mas também ignorará qualquer arquivo cujo nome esteja começando, bindependentemente 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.

Presumo que você usou ae bcomo espaços reservados e os nomes reais são mais parecidos com allosauruse brachiosaurus. Se você colocar brachiosaurusno lugar b, a quantidade de falsos positivos será drasticamente reduzida.

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

find a -not -path "*/b*" -type f -depth
lesmana
fonte
-not -pathfoi exatamente isso! Obrigado por uma explicação generosa!
forthrin
1
Alguma elaboração sobre por que -not -pathfunciona enquanto -prunenão seria útil. Por que -not -pathcoexistir -depth?
Faheem Mitha
3

Basta usar em rmvez de -delete:

find a -name b -prune -o -type f -exec rm -f {} +
Stéphane Chazelas
fonte
1
Você pode explicar por que rmfunciona e deletenão funciona ?
Faheem Mitha
1
Ah, acho que talvez porque "-delete se recuse a excluir diretórios não vazios.", Para citar @lesmana. Portanto, se recusa a excluir diretórios não vazios. Mas rmnão tem esse problema. Mas, independentemente, a elaboração seria uma coisa boa.
Faheem Mitha
@FaheemMitha, a resposta está na pergunta. -deleteimplica -depth, que obviamente não pode trabalhar com -prune. -pathfunciona, mas não para findde descer em diretórios que não precisam ser explorados.
Stéphane Chazelas 02/02/2019
0

As respostas e explicações acima foram muito úteis.

Eu uso as soluções alternativas de "-exec rm {} +" ou "-not -path ... -delete ', mas essas podem ser muito mais lentas que" find ... -delete ". Eu já vi" find ... -delete "executa 5x mais rápido que" -exec rm {} + "em diretórios profundos em um sistema de arquivos NFS.

A solução '-not path "tem a sobrecarga óbvia de examinar todos os arquivos nos diretórios excluídos e abaixo.

O "find .. -exec rm {} +" chama rm, que faz chamadas de sistema:

fstatat(AT_FDCWD, path...); 
unlinkat(AT_FDCWD, path, 0)

O "find -delete" faz chamadas do sistema:

 fd=open(dir,...);
 fchdir(fd); 
 fstatat(AT_FDCWD, filename,...)
 unlinkat(dirfd, filename,...)

Portanto, o comando "-exec rm {} +" rm faz o caminho completo para a pesquisa de inode duas vezes por arquivo, mas "find -delete" faz uma estatística e desvincula o nome do arquivo no diretório atual. Essa é uma grande vitória quando você está removendo muitos arquivos em um diretório.

(modo de lamentação ativado (desculpe))

Parece que o design da interação entre -thp, -delete e -prune elimina desnecessariamente a maneira mais eficiente de executar a ação comum "excluir arquivos, exceto os diretórios -prune"

A combinação de "-type f -delete" deve poder ser executada sem -thpth, pois não está tentando excluir diretórios. Como alternativa, se "find" tivesse uma ação "-deletefile" que diz não excluir diretórios, -thp não precisaria ser implícito.

O comando xargs ou find -exec calls to rm pode ser acelerado se o rm tiver a opção de classificar nomes de arquivos, abrir diretórios e desassociar (dir_fd, filename) em vez de desvincular os caminhos completos. Ele já desassocia (dir_fd, nome do arquivo) ao recursar nos diretórios com a opção -r.

(modo de lamentação desativado)

Donald Mears
fonte