Remove todos, mas todos os 12 arquivos

14

Eu tenho alguns milhares de arquivos no formato filename.12345.end. Eu só quero manter todos os 12 arquivos, então file.00012.end, file.00024.end ... file.99996.end e delete todo o resto.

Os arquivos também podem ter números anteriores em seu nome de arquivo e normalmente são da forma: file.00064.name.99999.end

Eu uso o Bash shell e não consigo descobrir como fazer o loop dos arquivos e, em seguida, obter o número e verificar se é number%%12=0 excluir o arquivo, se não. Alguém pode me ajudar?

Obrigado Dorina

    
por Dorina 12.09.2016 / 15:27

6 respostas

18

Aqui está uma solução Perl. Isso deve ser muito mais rápido para milhares de arquivos:

perl -e '@bad=grep{/(\d+)\.end/ &&  % 12 != 0}@ARGV; unlink @bad' *

Que pode ser ainda mais condensado em:

perl -e 'unlink grep{/(\d+)\.end/ &&  % 12 != 0}@ARGV;' *

Se você tiver muitos arquivos e não puder usar o simples * , poderá fazer algo como:

perl -e 'opendir($d,"."); unlink grep{/(\d+)\.end/ &&  % 12 != 0} readdir($dir)'

Quanto à velocidade, aqui está uma comparação dessa abordagem e da shell fornecida em uma das outras respostas:

$ touch file.{01..64}.name.{00001..01000}.end
$ ls | wc
  64000   64000 1472000
$ time for f in ./* ; do file="${f%.*}"; if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then rm "$f"; fi; done

real    2m44.258s
user    0m9.183s
sys     1m7.647s

$ touch file.{01..64}.name.{00001..01000}.end
$ time perl -e 'unlink grep{/(\d+)\.end/ &&  % 12 != 0}@ARGV;' *

real    0m0.610s
user    0m0.317s
sys     0m0.290s

Como você pode ver, a diferença é enorme, conforme o esperado .

Explicação

  • O -e está simplesmente informando perl para executar o script fornecido na linha de comando.
  • @ARGV é uma variável especial que contém todos os argumentos dados ao script. Como estamos dando * , ele conterá todos os arquivos (e diretórios) no diretório atual.
  • O grep pesquisará a lista de nomes de arquivos e procurará qualquer que corresponda a uma sequência de números, um ponto e end ( /(\d+)\.end/) .

  • Como os números ( \d ) estão em um grupo de captura (parênteses), eles são salvos como . Portanto, o grep verificará se esse número é um 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 unlink() , o que remove os arquivos (mas não os diretórios).

por terdon 12.09.2016 / 18:15
12

Dado que os seus nomes de arquivos estão no formato file.00064.name.99999.end , precisamos primeiro eliminar tudo, exceto nosso número. Usaremos um loop for para fazer isso.

Nós também precisamos dizer ao Bash shell para usar a base 10, porque a aritmética do Bash tratará os números começando com 0 como base 8, o que vai bagunçar as coisas para nós.

Como um script, para ser lançado quando no diretório que contém arquivos, use:

#!/bin/bash

for f in ./*
do
  if [[ -f "$f" ]]; then
    file="${f%.*}"
    if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then
      rm "$f"
    fi
  else
    echo "$f is not a file, skipping."
  fi
done

Ou você pode usar esse longo e feio comando para fazer a mesma coisa:

for f in ./* ; do if [[ -f "$f" ]]; then file="${f%.*}"; if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then rm "$f"; fi; else echo "$f is not a file, skipping."; fi; done

Para explicar todas as partes:

  • for f in ./* significa tudo no diretório atual, do .... Isso define cada arquivo ou diretório localizado como a variável $ f.
  • if [[ -f "$f" ]] verifica se o item encontrado é um arquivo, caso contrário, passamos para a echo "$f is not... part, 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 que aparece após o último . .
  • if [[ $((10#${file##*.} % 12)) -eq 0 ]] é onde a aritmética principal entra em ação. O ${file##*.} reduz tudo antes do último . em nosso nome de arquivo sem extensão. $(( $num % $num2 )) é a sintaxe da aritmética do Bash para usar a operação do módulo, o 10# no início diz ao Bash para usar a base 10, para lidar com aqueles 0s iniciais incômodos. $((10#${file##*.} % 12)) deixa-nos o resto do nosso número de nomes de arquivos dividido por 12. -ne 0 verifica se o resto "não é igual" a zero.
  • Se o restante não for igual a 0, o arquivo será excluído com o comando rm , talvez você queira substituir rm por echo ao executar isso pela primeira vez, para verificar se os arquivos esperados serão excluídos .

Esta solução não é recursiva, o que significa que ela só processará arquivos no diretório atual, não entrará em nenhum subdiretório.

A instrução if com o comando echo para avisar sobre diretórios não é realmente necessária, pois rm por conta própria irá reclamar dos diretórios e não os excluirá, portanto:

#!/bin/bash

for f in ./*
do
  file="${f%.*}"
  if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then
    rm "$f"
  fi
done

Ou

for f in ./* ; do file="${f%.*}"; if [[ $((10#${file##*.} % 12)) -ne 0 ]]; then rm "$f"; fi; done

Funcionará corretamente também.

    
por Arronical 12.09.2016 / 17:10
6

Você pode usar a expansão de suporte Bash para gerar nomes contendo cada 12º número. Vamos criar alguns dados de teste

$ touch file.{0..9}{0..9}{0..9}{0..9}{0..9}.end # create test data
$ mv file.00024.end file.00024.end.name.99999.end # testing this form of filenames

Então podemos usar o seguinte

$ ls 'file.'{00012..100..12}* # print these with numbers less than 100
file.00012.end                 file.00036.end  file.00060.end  file.00084.end
file.00024.end.name.99999.end  file.00048.end  file.00072.end  file.00096.end
$ rm 'file.'{00012..100000..12}* # do the job

Funciona irremediavelmente lento para uma grande quantidade de arquivos - leva tempo e memória para gerar milhares de nomes - então é mais um truque que uma solução eficiente real.

    
por Nykakin 13.09.2016 / 10:34
1

Um pouco longo, mas é o que me veio à mente.

 for num in $(seq 1 1 11) ; do
     for sequence in $(seq -f %05g $num 12 99999) ; do
         rm file.$sequence.end.99999;
     done
 done

Explicação: Apague todos os 12 arquivos onze vezes.

    
por Terrik 13.09.2016 / 17:59
0

Em toda a humildade eu acho que esta solução é muito melhor do que a outra resposta:

find . -name '*.end' -depth 1 | awk 'NR%12 != 0 {print}' | xargs -n100 rm

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 em uma profundidade de 1 (isto é, eles estão diretamente no diretório de trabalho e não em quaisquer subpastas. Você pode deixar isso de lado se não houver subpastas) . A lista de saída será classificada em ordem alfabética.

Em seguida, enviamos essa lista para awk , onde usamos a variável especial NR , que é o número da linha. Deixamos de fora todos os 12 arquivos, imprimindo os arquivos em que NR%12 != 0 . O comando awk pode ser encurtado para awk 'NR%12' , porque o resultado do operador de módulo é interpretado como um valor booleano e o {print} é implicitamente feito de qualquer maneira.

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, você receberá um erro dizendo 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 pelo -n 100 flag, que separa os argumentos a cada 100 palavras (não linhas, algo a observar se os nomes dos arquivos tiverem espaços) e executa um comando rm separado, cada um com apenas 100 argumentos.

    
por user593851 12.09.2016 / 18:16
0

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 é um múltiplo de 12) e 2. excluir todos os arquivos restantes o diretório, em seguida, 3. coloque os múltiplos de 12 arquivos que você manteve de volta onde eles estavam. Então, algo assim pode funcionar:

cd dir_containing_files
mkdir keep_these_files
n=0
while [ "${n}" -lt 99999 ]; do
  padded_n="'echo -n "00000${n}" | tail -c 5'"
  mv "filename${padded_n}.end" keep_these_files/
  n=$[n+12]
done
rm filename*.end
mv keep_these_files/* .
rmdir keep_these_files
    
por delt 14.09.2016 / 03:42

Tags