Exclua todos os arquivos n mais recentes para cada grupo de arquivos que compartilham o mesmo prefixo em um diretório

4

Minha pergunta é um pouco diferente de algumas perguntas mais antigas, simplesmente pedindo "excluindo todos os arquivos n mais recentes em um diretório".

Eu tenho um diretório que contém diferentes 'grupos' de arquivos onde cada grupo de arquivos compartilha um prefixo arbitrário e cada grupo tem pelo menos um arquivo. Eu não conheço esses prefixos com antecedência e não sei quantos grupos existem.

EDIT: na verdade, eu sei algo sobre os nomes dos arquivos, ou seja, todos seguem o padrão prefix-some_digits-some_digits.tar.bz2 . A única coisa que importa aqui é a parte prefix , e podemos supor que dentro de cada prefix não há dígito ou traço.

Eu quero fazer o seguinte em um script bash :

  1. Percorra o diretório fornecido, identifique todos os "grupos" existentes e, para cada grupo de arquivos, exclua todos os arquivos n mais recentes, exceto os grupos mais recentes.

  2. Se houver menos de n arquivos para um grupo, não faça nada para esse grupo, ou seja, não exclua nenhum arquivo desse grupo.

O que é uma maneira robusta e segura de fazer o acima em bash ? Você poderia por favor explicar os comandos passo-a-passo?

    
por skyork 03.11.2015 / 21:28

4 respostas

1

O script:

#!/bin/bash

# Get Prefixes

PREFIXES=$(ls | grep -Po '^(.*)(?!HT\d{4})-(.*)-(.*).tar.bz2$' | awk -F'-' '{print $1}' | uniq)

if [ -z "$1" ]; then
  echo need a number of keep files.
  exit 1
else
  NUMKEEP=$1
fi

for PREFIX in ${PREFIXES}; do

  ALL_FILES=$(ls -t ${PREFIX}*)

  if [ $(echo ${ALL_FILES} | wc -w) -lt $NUMKEEP ]; then
    echo Not enough files to be kept. Quit.
    continue
  fi

  KEEP=$(ls -t ${PREFIX}* | head -n${NUMKEEP})

  for file in $ALL_FILES ; do
    if [[ "$KEEP" =~ "$file" ]]; then
      echo keeping $file
    else
      echo RM $file
    fi
  done
done

Explicação:

  • Calcule os prefixos:
    • Procure todos os arquivos seguindo o something-something-something.tar.bz2 regex, cortando apenas a primeira parte até o primeiro traço e tornando-a única.
    • o resultado é uma lista normalizada do PREFIXES
  • Iterar todos os PREFIXES :
  • Calcular ALL_FILES com PREFIX
  • Verifique se a quantia de ALL_FILES é menor que o número de arquivos a serem mantidos - > se for verdade, podemos parar por aqui, nada para remover
  • Calcule os arquivos KEEP que são os arquivos NUMKEEP mais recentes
  • Altere por ALL_FILES e verifique se o arquivo fornecido não está na lista de arquivos KEEP . Em caso afirmativo: remova-o.

Exemplo de resultado ao executá-lo:

$ ./remove-old.sh 2
keeping bar-01-01.tar.bz2
keeping bar-01-02.tar.bz2
RM bar-01-03.tar.bz2
RM bar-01-04.tar.bz2
RM bar-01-05.tar.bz2
RM bar-01-06.tar.bz2
keeping foo-01-06.tar.bz2
keeping foo-01-05.tar.bz2
RM foo-01-04.tar.bz2
RM foo-01-03.tar.bz2
RM foo-01-02.tar.bz2

$ ./remove-old.sh 8
Not enough files to be kept. Quit.
Not enough files to be kept. Quit.
    
por 03.11.2015 / 21:57
1

Assumirei que os arquivos são agrupados pelo prefixo quando listados em ordem lexical. Isso significa que não há grupos com um prefixo que seja um sufixo de outro grupo, por exemplo, não foo-1-2-3.tar.bz2 que ficaria entre foo-1-1.tar.bz2 e foo-1-2.tar.bz2 . Sob este pressuposto, podemos listar todos os arquivos, e quando detectamos uma mudança de prefixo (ou para o primeiro arquivo), temos um novo grupo.

#!/bin/bash
n=$1; shift   # number of files to keep in each group
shopt extglob
previous_prefix=-
for x in *-+([0-9])-+([0-9]).tar.bz2; do
  # Step 1: skip the file if its prefix has already been processed
  this_prefix=${x%-+([0-9])-+([0-9]).tar.bz2}
  if [[ "$this_prefix" == "$previous_prefix" ]]; then
    continue
  fi
  previous_prefix=$this_prefix
  # Step 2: process all the files with the current prefix
  keep_latest "$n" "$this_prefix"-+([0-9])-+([0-9]).tar.bz2
done

Agora, temos o problema de determinando os arquivos mais antigos entre uma lista explícita .

Supondo que os nomes dos arquivos não contenham novas linhas ou caracteres que ls não exiba literalmente, isso pode ser implementado com ls :

keep_latest () (
  n=$1; shift
  if [ "$#" -le "$n" ]; then return; fi
  unset IFS; set -f
  set -- $(ls -t)
  shift "$n"
  rm -- "$@"
)
    
por 04.11.2015 / 00:49
1

Eu sei que isso é marcado como bash , mas acho que isso seria mais fácil com zsh :

#!/usr/bin/env zsh

N=$(($1 + 1))                         # calculate Nth to last
typeset -U prefixes                   # declare array with unique elements
prefixes=(*.tar.bz2(:s,-,/,:h))       # save prefixes in the array
for p in $prefixes                    # for each prefix
do
arr=(${p}*.tar.bz2)                   # save filenames starting with prefix in arr
if [[ ${#arr} -gt $1 ]]               # if number of elements is greather than $1
then
print -rl -- ${p}*.tar.bz2(Om[1,-$N]) # print all filenames but the most recent N 
fi
done

o script aceita um argumento: n (o número de arquivos)% (:s,-,/,:h) são modificadores glob, :s substitui o primeiro - por / e :h extrai a cabeça (a parte até a última barra que, nesse caso, também é a primeira barra, pois há apenas uma)
(Om[1,-$N]) são qualificadores glob, Om classifica os arquivos que começam com o mais antigo e [1,-$N] seleciona do primeiro até o enésimo para durar um Se você estiver satisfeito com o resultado, substitua print -rl por rm para excluir os arquivos, por exemplo:

#!/usr/bin/env zsh

typeset -U prefixes
prefixes=(*.tar.bz2(:s,-,/,:h))
for p in $prefixes
arr=(${p}*.tar.bz2) && [[ ${#arr} -gt $1 ]] && rm -- ${p}*.tar.bz2(Om[1,-$(($1+1))])
    
por 04.11.2015 / 01:55
1

Conforme solicitado, essa resposta tende a ser "robusta e segura" conforme solicitado, em vez de rápida & sujo.

Portabilidade: essa resposta funciona em qualquer sistema que contenha sh , find , sed , sort , ls , grep , xargs e rm .

O script nunca deve se afogar em um diretório grande. Nenhuma expansão de nome de arquivo de shell é executada (o que poderia afogar se muitos arquivos, mas isso é um número enorme).

Esta resposta assume que o prefixo não conterá nenhum traço ( - ).

Observe que, por design, o script lista apenas os arquivos que serão removidos. Você pode fazer com que ele remova os arquivos canalizando a saída do loop while para xargs -d '/n' rm , o que é comentado no script. Dessa forma, você pode testar facilmente o script antes de ativar o código de remoção.

#!/bin/sh -e

NUM_TO_KEEP=$(( 0 + ${1:-64000} )) || exit 1

find . -maxdepth 1 -regex '[^-][^-]*-[0-9][0-9]*-[0-9][0-9]*.tar.bz2' |
sed 's/-.*//; s,^\./,,' |
sort -u |
while read prefix
do
    ls -t | grep  "^$prefix-.*-.*\.tar\.bz2$" | sed "1,$NUM_TO_KEEP d"
done # | xargs -d '\n' rm --

O parâmetro N (número de arquivos a manter) é padronizado para 64000 (ou seja, todos os arquivos são mantidos).

Código anotado

Obtenha o argumento da linha de comando e verifique se há integer por adição, se não for fornecido o padrão do parâmetro para 64000 (efetivamente todos):

NUM_TO_KEEP=$(( 0 + ${1:-64000} )) || exit 1

Encontre todos os arquivos no diretório atual que correspondam ao formato do nome do arquivo:

find . -maxdepth 1 -regex '[^-][^-]*-[0-9][0-9]*-[0-9][0-9]*.tar.bz2' |

Obtenha o prefixo: remova tudo após o prefixo e remova o "./" no início:

sed 's/-.*//; s,^\./,,' |

Ordene os prefixos e remova os duplicados ( -u - unique):

sort -u |

Leia cada prefixo e processo:

while read prefix
do

Liste todos os arquivos no diretório classificados por hora, selecione os arquivos para o prefixo atual e exclua todas as linhas além dos arquivos que queremos manter:

    ls -t | grep  "^$prefix-.*-.*\.tar\.bz2$" | sed "1,$NUM_TO_KEEP d"

Para testar, comente o código para remover o arquivo. Usando xargs para evitar qualquer problema com o tamanho da linha de comando ou espaços em nomes de arquivos, se houver. Se você quiser que o script produza um log, adicione -v a rm , por exemplo: rm -v -- . Remova o # para ativar o código de remoção:

done # | xargs -d '\n' rm --

Se isso funcionar para você, aceite esta resposta e vote. Obrigado.

    
por 04.11.2015 / 00:45