Elimine todos los archivos n excepto el más reciente para cada grupo de archivos que comparten el mismo prefijo en un directorio

Elimine todos los archivos n excepto el más reciente para cada grupo de archivos que comparten el mismo prefijo en un directorio

Mi pregunta es un poco diferente de algunas preguntas anteriores que simplemente piden "eliminar todos nlos archivos excepto los más recientes en un directorio".

Tengo un directorio que contiene diferentes 'grupos' de archivos donde cada grupo de archivos comparte un prefijo arbitrario y cada grupo tiene al menos un archivo. No conozco estos prefijos de antemano y no sé cuántos grupos hay.

EDITAR: en realidad, sé algo sobre los nombres de los archivos, es decir, todos siguen el patrón prefix-some_digits-some_digits.tar.bz2. Aquí lo único que importa es la prefixparte, y podemos suponer que dentro de cada una prefixno hay dígito ni guión.

Quiero hacer lo siguiente en un bashscript:

  1. Vaya al directorio proporcionado, identifique todos los 'grupos' existentes y, para cada grupo de archivos, elimine todos nlos archivos del grupo excepto los más recientes.

  2. Si hay menos de narchivos para un grupo, no haga nada para ese grupo, es decir, no elimine ningún archivo para ese grupo.

¿Cuál es una forma sólida y segura de hacer lo anterior bash? ¿Podrías explicar los comandos paso a paso?

Respuesta1

La secuencia de comandos:

#!/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

Explicación:

  • Calcula los prefijos:
    • Busque todos los archivos que siguen la something-something-something.tar.bz2expresión regular, cortando solo la primera parte hasta el primer guión y hágalo único.
    • el resultado es una lista normalizada de losPREFIXES
  • Iterar a través de todo PREFIXES:
  • Calcular ALL_FILESconPREFIX
  • Compruebe si la cantidad ALL_FILESes menor que la cantidad de archivos que se conservarán -> si es cierto, podemos detenernos aquí, no hay nada que eliminar
  • Calcular los archivos cuales son los archivos KEEPmás recientes .NUMKEEP
  • Repita ALL_FILESy verifique si el archivo dado no está en la KEEPlista de archivos. Si es así: retírelo.

Resultado de ejemplo al ejecutarlo:

$ ./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.

Respuesta2

Según lo solicitado, esta respuesta tiende a ser "robusta y segura" como usted solicitó, en lugar de rápida y sucia.

Portabilidad: esta respuesta funciona en cualquier sistema que contenga sh, find, sed, sort, ls, grep, xargsy rm.

El script nunca debería ahogarse en un directorio grande. No se realiza ninguna expansión del nombre de archivo del shell (lo que podría bloquearse si hay demasiados archivos, pero es una cantidad enorme).

Esta respuesta supone que el prefijo no contendrá ningún guión ( -).

Tenga en cuenta que, por diseño, el script solo enumera los archivos que se eliminarán. Puede hacer que elimine los archivos canalizando la salida del whilebucle al xargs -d '/n' rmque está comentado en el script. De esta manera, puede probar fácilmente el script antes de habilitar el código de eliminación.

#!/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 --

El parámetro N (número de archivos a conservar) tiene por defecto 64000 (es decir, se conservan todos los archivos).

Código anotado

Obtenga el argumento de la línea de comando y verifique el número entero mediante la suma; si no se le proporciona, el parámetro predeterminado es 64000 (efectivamente, todos):

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

Busque todos los archivos en el directorio actual que coincidan con el formato de nombre de archivo:

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

Obtener prefijo: elimine todo lo que esté después del prefijo y elimine "./" al principio:

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

Ordene los prefijos y elimine los duplicados ( -u-único):

sort -u |

Lea cada prefijo y proceso:

while read prefix
do

Enumere todos los archivos en el directorio ordenados por tiempo, seleccione los archivos para el prefijo actual y elimine todas las líneas más allá de los archivos que queremos conservar:

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

Para realizar pruebas, comente el código para eliminar el archivo. Usar xargs para evitar problemas con la longitud de la línea de comando o los espacios en los nombres de archivos, si los hay. Si desea que el script produzca un registro, agréguelo, -vpor rmejemplo: rm -v --. Elimine #para habilitar el código de eliminación:

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

Si esto funciona para usted, acepte esta respuesta y vote a favor. Gracias.

Respuesta3

Asumiré que los archivos están agrupados por prefijo cuando se enumeran en orden léxico. Esto significa que no hay grupos con un prefijo que sea sufijo de otro grupo, por ejemplo, no, foo-1-2-3.tar.bz2que se interponga entre foo-1-1.tar.bz2y foo-1-2.tar.bz2. Bajo esta suposición, podemos enumerar todos los archivos y cuando detectamos un cambio de prefijo (o para el primer archivo), tenemos un nuevo 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

Ahora nos encontramos con el problema dedeterminar los archivos más antiguos entre una lista explícita.

Suponiendo que los nombres de los archivos no contienen nuevas líneas o caracteres que lsno se muestran literalmente, esto se puede implementar con ls:

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

Respuesta4

Sé que esto está etiquetado bashpero creo que sería más fácil con 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

el script acepta un argumento:norte(el número de archivos)
(:s,-,/,:h)son modificadores globales, :sreemplaza el primero -y /extrae :hel encabezado (la parte hasta la última barra que en este caso también es la primera barra ya que solo hay una)
(Om[1,-$N])son calificadores globales, Omordena los archivos comenzando con el más antiguo y [1,-$N]selecciona desde el primero hasta el enésimo hasta el último.
Si está satisfecho con el resultado, reemplácelo print -rlcon rmpara eliminar los archivos, por ejemplo:

#!/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))])

información relacionada