Mi pregunta es un poco diferente de algunas preguntas anteriores que simplemente piden "eliminar todos n
los 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 prefix
parte, y podemos suponer que dentro de cada una prefix
no hay dígito ni guión.
Quiero hacer lo siguiente en un bash
script:
Vaya al directorio proporcionado, identifique todos los 'grupos' existentes y, para cada grupo de archivos, elimine todos
n
los archivos del grupo excepto los más recientes.Si hay menos de
n
archivos 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.bz2
expresión regular, cortando solo la primera parte hasta el primer guión y hágalo único. - el resultado es una lista normalizada de los
PREFIXES
- Busque todos los archivos que siguen la
- Iterar a través de todo
PREFIXES
: - Calcular
ALL_FILES
conPREFIX
- Compruebe si la cantidad
ALL_FILES
es 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
KEEP
más recientes .NUMKEEP
- Repita
ALL_FILES
y verifique si el archivo dado no está en laKEEP
lista 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
, xargs
y 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 while
bucle al xargs -d '/n' rm
que 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, -v
por rm
ejemplo: 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.bz2
que se interponga entre foo-1-1.tar.bz2
y 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 ls
no 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 bash
pero 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, :s
reemplaza el primero -
y /
extrae :h
el 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, Om
ordena 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 -rl
con rm
para 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))])