Eu tenho alguns milhares de arquivos no formato filename.12345.end. Eu só quero manter cada 12º arquivo, então file.00012.end, file.00024.end ... file.99996.end e exclua todo o resto.
Os arquivos também podem ter números anteriores no nome do arquivo e normalmente têm o formato: file.00064.name.99999.end
Eu uso o shell Bash e não consigo descobrir como fazer um loop sobre os arquivos e, em seguida, obter o número e verificar se ele está number%%12=0
excluindo o arquivo, se não estiver. Alguém pode me ajudar?
Obrigado Dorina
Respostas:
Aqui está uma solução Perl. Isso deve ser muito mais rápido para milhares de arquivos:
Que pode ser ainda mais condensado em:
Se você tiver muitos arquivos e não puder usar o simples
*
, poderá fazer algo como:Quanto à velocidade, aqui está uma comparação dessa abordagem e a shell fornecida em uma das outras respostas:
Como você pode ver, a diferença é enorme, como esperado .
Explicação
-e
simplesmente está dizendoperl
para executar o script fornecido na linha de comando.@ARGV
é uma variável especial que contém todos os argumentos fornecidos ao script. Como estamos fornecendo*
, ele conterá todos os arquivos (e diretórios) no diretório atual.O
grep
pesquisará a lista de nomes de arquivos e procurará por qualquer que corresponda a uma sequência de números, um ponto eend
(/(\d+)\.end/)
.Como os números (
\d
) estão em um grupo de captura (parênteses), eles são salvos como$1
. Portantogrep
, verificará se esse número é múltiplo de 12 e, se não for, o nome do arquivo será retornado. Em outras palavras, a matriz@bad
contém a lista de arquivos a serem excluídos.A lista é então passada para a
unlink()
qual remove os arquivos (mas não os diretórios).fonte
Como seus nomes de arquivo estão no formato
file.00064.name.99999.end
, primeiro precisamos aparar tudo, exceto nosso número. Usaremos umfor
loop para fazer isso.Também precisamos dizer ao shell do Bash para usar a base 10, porque a aritmética do Bash tratará os números começando com um 0 como base 8, o que irá atrapalhar as coisas para nós.
Como um script, para ser iniciado quando no diretório que contém arquivos, use:
Ou você pode usar este comando muito longo e feio para fazer a mesma coisa:
Para explicar todas as partes:
for f in ./*
significa para tudo no diretório atual, faça .... Isso define cada arquivo ou diretório encontrado como a variável $ f.if [[ -f "$f" ]]
verifica se o item encontrado é um arquivo; caso contrário, saltamos para aecho "$f is not...
parte, o que significa que não começamos a excluir diretórios acidentalmente.file="${f%.*}"
define a variável $ file como o nome do arquivo aparando o que vier depois da última.
.if [[ $((10#${file##*.} % 12)) -eq 0 ]]
é onde a aritmética principal entra em ação. Ela${file##*.}
apara tudo antes da última.
em nosso nome de arquivo sem extensão.$(( $num % $num2 ))
é a sintaxe da aritmética do Bash para usar a operação de módulo,10#
no início diz ao Bash para usar a base 10, para lidar com os 0s iniciais traquinas.$((10#${file##*.} % 12))
então nos deixa o restante do número de nossos nomes de arquivos dividido por 12.-ne 0
verifica se o restante é "diferente de zero".rm
comando, você poderá substituí-lorm
pelaecho
primeira vez, para verificar se os arquivos esperados serão excluídos.Essa solução não é recursiva, o que significa que processará apenas os arquivos no diretório atual e não entrará em nenhum subdiretório.
A
if
declaração com oecho
comando para avisar sobre diretórios não é realmente necessária, poisrm
, por si só, reclamará de diretórios e não os excluirá, portanto:Ou
Também funcionará corretamente.
fonte
rm
alguns milhares de vezes pode ser bem lento. Sugiroecho
o nome do arquivo em vez disso e canalizar a saída do loop paraxargs rm
(opções de adicionar como necessário):for f in *; do if ... ; then echo "$f"; fi; done | xargs -rd '\n' -- rm --
.xargs
levou 2 minutos e 48 segundos e a versão levou 5 minutos e 1 segundo. Isso pode serecho
causado por sobrecarga no @DavidFoerster?time { for f in *; do echo "$f"; done | xargs rm; }
vs. 1m11.450s / 0m10.695s / 0m16.800s comtime { for f in *; do rm "$f"; done; }
um tmpfs. O Bash é a v4.3.11, o kernel é a v4.4.19.Você pode usar a expansão de colchete Bash para gerar nomes contendo cada 12º número. Vamos criar alguns dados de teste
Então podemos usar o seguinte
No entanto, funciona desesperadamente lento para uma grande quantidade de arquivos - leva tempo e memória para gerar milhares de nomes - por isso é mais um truque que a solução eficiente.
fonte
Um pouco longo, mas é o que me veio à mente.
Explicação: Exclua cada 12º arquivo onze vezes.
fonte
Com toda humildade, acho que essa solução é muito melhor do que a outra resposta:
Uma pequena explicação: Primeiro, geramos uma lista de arquivos com
find
. Obtemos todos os arquivos cujo nome termina com.end
e que estão na profundidade de 1 (ou seja, eles estão diretamente no diretório de trabalho e não em nenhuma subpasta. Você pode deixar isso de fora se não houver subpastas). A lista de saída será classificada em ordem alfabética.Em seguida, canalizamos essa lista para
awk
onde usamos a variável especialNR
que é o número da linha. Deixamos de fora todos os arquivos 12, imprimindo os arquivos ondeNR%12 != 0
. Oawk
comando pode ser abreviado paraawk 'NR%12'
, porque o resultado do operador módulo é interpretado como um valor booleano e, de{print}
qualquer forma, é implicitamente feito.Portanto, agora temos uma lista de arquivos que precisam ser excluídos, o que podemos fazer com xargs e rm.
xargs
executa o comando fornecido (rm
) com a entrada padrão como argumentos.Se você tiver muitos arquivos, receberá um erro ao dizer algo como 'lista de argumentos muito longa' (na minha máquina esse limite é de 256 kB e o mínimo exigido pelo POSIX é de 4096 bytes). Isso pode ser evitado pela
-n 100
bandeira, que divide os argumentos a cada 100 palavras (não linhas, algo a ser observado se os nomes dos arquivos tiverem espaços) e executa umrm
comando separado , cada um com apenas 100 argumentos.fonte
-depth
precisa ser anterior-name
; ii) isso falhará se algum dos nomes de arquivo contiver espaço em branco; iii) você está assumindo que os arquivos serão listados em ordem numérica crescente (é para isso que vocêawk
está testando), mas isso quase certamente não será o caso. Portanto, isso excluirá um conjunto aleatório de arquivos.-depth
. Ainda assim, esse foi o menor dos problemas aqui, o mais importante é que você está excluindo um conjunto aleatório de arquivos e não os que o OP deseja.-depth
não tem um valor e faz o oposto do que você pensa que faz. Vejaman find
: "-thp Processa o conteúdo de cada diretório antes do próprio diretório.". Portanto, isso realmente desce em subdiretórios e causa estragos por todo o lugar.-depth n
e-maxdepth n
existem. O primeiro requer que a profundidade seja exatamente n e, com o segundo, pode ser <= n. II) Sim, isso é ruim, mas para este exemplo em particular não é motivo de preocupação. Você pode corrigi-lo usandofind ... -print0 | awk 'BEGIN {RS="\0"}; NR%12 != 0' | xargs -0 -n100 rm
, que usa o byte nulo como separador de registros (o que não é permitido em nomes de arquivos). III) Mais uma vez, neste caso, a suposição é razoável. Caso contrário, você poderá inserir umsort -n
entrefind
eawk
, ou redirecionarfind
para um arquivo e classificá-lo como quiser.find
. Novamente, no entanto, o principal problema é que você está assumindo quefind
retorna uma lista classificada. Não faz.Para usar apenas o bash, minha primeira abordagem seria: 1. mover todos os arquivos que você deseja manter em outro diretório (ou seja, todos aqueles cujo número no nome do arquivo é múltiplo de 12) e depois 2. excluir todos os arquivos restantes no diretório, 3. coloque os múltiplos de 12 arquivos que você guardou onde estavam. Então, algo assim pode funcionar:
fonte
filename
peça se ela não é consistente?