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

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

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

Eu tenho um diretório que contém diferentes 'grupos' de arquivos onde cada grupo de arquivos compartilha algum prefixo arbitrário e cada grupo possui pelo menos um arquivo. Não conheço esses prefixos de antemão 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 prefixparte, e podemos assumir que dentro de cada uma prefixnão há dígito ou travessão.

Quero fazer o seguinte em um bashscript:

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

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

Qual é uma maneira robusta e segura de fazer o que foi dito acima bash? Você poderia explicar os comandos passo a passo?

Responder1

O roteiro:

#!/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 a something-something-something.tar.bz2regex, cortando apenas a primeira parte até o primeiro traço e torne-o único.
    • o resultado é uma lista normalizada dosPREFIXES
  • Iterar por tudo PREFIXES:
  • Calcular ALL_FILEScomPREFIX
  • Verifique se a quantidade ALL_FILESé menor que a quantidade de arquivos a serem mantidos -> se for verdade, podemos parar por aqui, nada a remover
  • Calcule os arquivos que são os arquivos KEEPmais recentesNUMKEEP
  • Itere ALL_FILESe verifique se o arquivo fornecido não está na KEEPlista de arquivos. Se sim: remova-o.

Resultado de exemplo 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.

Responder2

Conforme solicitado, esta resposta tende a ser "robusta e segura", conforme solicitado, em vez de rápida e suja.

Portabilidade: Esta resposta funciona em qualquer sistema que contenha sh, find, sed, sort, ls, grep, xargse rm.

O script nunca deve engasgar com um diretório grande. Nenhuma expansão de nome de arquivo do shell é executada (o que pode sufocar se houver muitos arquivos, mas esse é um número enorme).

Esta resposta pressupõe 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 whileloop para xargs -d '/n' rmo qual está 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 serem mantidos) é padronizado como 64000 (ou seja, todos os arquivos são mantidos).

Código anotado

Obtenha o argumento da linha de comando e verifique o número inteiro por adição, se não for fornecido o padrão do parâmetro é 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' |

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

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

Classifique os prefixos e remova duplicatas ( -u--único):

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 problemas com o comprimento da linha de comando ou espaços nos nomes de arquivos, se houver. Se você deseja que o script produza um log, adicione, -vpor rmexemplo: 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.

Responder3

Presumo que os arquivos sejam agrupados por prefixo quando listados em ordem lexical. Isso significa que não existem grupos com um prefixo que seja sufixo de outro grupo, por exemplo, não foo-1-2-3.tar.bz2que ficaria entre foo-1-1.tar.bz2e foo-1-2.tar.bz2. Partindo dessa suposição, 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 chegamos ao problema dedeterminando os arquivos mais antigos entre uma lista explícita.

Supondo que os nomes dos arquivos não contenham novas linhas ou caracteres que lsnão sejam exibidos 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 -- "$@"
)

Responder4

Eu sei que isso está marcado bash, mas acho que 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, :ssubstitui o primeiro -por /e :hextrai o cabeçalho (a parte até a última barra que neste caso também é a primeira barra, pois há apenas uma)
(Om[1,-$N])são qualificadores glob, Omclassifica os arquivos começando com o o mais antigo e [1,-$N]seleciona do primeiro ao enésimo até o último.
Se você estiver satisfeito com o resultado, substitua print -rlpor rmpara realmente 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))])

informação relacionada