
Me dieron un problema y mi solución pasó los casos de prueba iniciales pero falló el 50% al enviarla.
El problema: un directorio contiene varios archivos y carpetas, algunos de estos archivos son diferentes tipos de registros; error.log, error.log.1, error.log.2, access.log.1, access.log.2 ..etc. El contenido de esos archivos se asigna al día siguiente, por lo que 'cat error.log.1' tiene 'registros del día 2'... etc.
La tarea consiste en incrementar el número al final de los registros únicamente y dejar el resto del contenido del directorio sin cambios. Además, cree un archivo vacío para cada tipo de registro.
Por ejemplo:
./
example_dir
example2_dir
error.log
error.log.1
info.log.20
access.log.1
readme.txt
El script cambia el directorio a:
./
example_dir (unchanged)
example2_dir (unchanged)
error.log (empty)
error.log.1 (originally error.log)
error.log.2 (originally error.log.1)
info.log (empty)
info.log.21 (originally info.log.20)
access.log (empty)
access.log.2 (originally access.log.1)
readme.txt (unchanged)
Condiciones: # Archivos en el directorio < 1000, Max # Archivos por tipo < 21
Mi solución:
#!/bin/bash
declare -a filenames
# Renaming in ascending order will lead to overwrite; so start rename from the bottom
files=$(find . -maxdepth 1 -name "*.log.*" -exec basename {} \; | sort -rn)
for i in $files; do
currentFileNumber=$(echo -e "$i" | sed -e 's/[^0-9]*//g') # Extract the current number from the filename
fileName=$(echo -e "$i" | sed -e 's/\.[0-9]*$//g') # Extract the name without the trailing number
newFileNumber=$(("$currentFileNumber" + 1)) # Increment the current number
mv "$i" "$fileName.$newFileNumber" # Rename and append the incremented value
if [[ ! ${filenames[*]} =~ ${fileName} ]] # Store names of existing types to create empty files
then
filenames=("${filenames[@]}" "${fileName}")
fi
# Could make use of [[ -e "$fileName.log" ]] instead of an array, but won't pass the test for some reason
done
for j in "${filenames[@]}"; do touch "$j"; done # Create the empty files
unset filenames
No mostró los casos de prueba en los que fallé, por lo que no estoy seguro de cómo resolver esto mejor.
Respuesta1
Este fue un ejercicio divertido, así que aquí está mi solución.
#/bin/bash
log_names=$(for logfile in $(find . -type f -name '*.log*'); do echo ${logfile%.[0-9]*}; done | sort -u)
for name in $log_names; do
echo "Processing $name"
i=20
until [[ "$i" -eq 0 ]]; do
if [[ -f "$name.$i" ]]; then
next_num=$((i+1))
mv -v "$name.$i" "$name.$next_num"
fi
i=$((i-1))
done
if [[ -f "$name" ]]; then
mv -v "$name" "$name.1"
fi
touch "$name"
done
La variable log_names utiliza un find
comando para obtener la lista de archivos de registro. Luego, aplico una sustitución de cadena para eliminar el sufijo numérico. Después de eso, clasifico y elimino los duplicados.
En este punto, obtengo una lista de los nombres de archivos de registro únicos en el directorio: ./access.log ./error.log ./info.log
.
Luego, proceso cada nombre por turno usando un for
bucle.
Ahora, para cada archivo, nos han dicho que el número máximo posible es 20. Comenzamos allí y usamos un until
bucle para realizar la cuenta regresiva.
La mv
lógica es simple: si 'filname.number' existe, muévalo a 'filename.(number+1)'.
Cuando until
finaliza el ciclo (i = 0), es posible que nos quede un archivo sin rotar, el que no tiene el sufijo numérico. Si es así, muévalo al nombre de archivo.1.
El último paso es crear un archivo vacío con touch
.
Ejecución de muestra:
$ ls
access.log.1 error.log error.log.1 example_dir example2_dir info.log.20 readme.txt rotate.bash
$ bash rotate.bash
Processing ./access.log
'./access.log.1' -> './access.log.2'
Processing ./error.log
'./error.log.1' -> './error.log.2'
'./error.log' -> './error.log.1'
Processing ./info.log
'./info.log.20' -> './info.log.21'
$ ls -1
access.log
access.log.2
error.log
error.log.1
error.log.2
example_dir
example2_dir
info.log
info.log.21
readme.txt
rotate.bash
Respuesta2
@Haxiel ha publicado una solución. Esto es similar al que tenía en mente y al que describí como "más sencillo". Habría usado un for
bucle en lugar del until
bucle.
Esto es algo que utiliza casi la cantidad mínima de procesos externos, uno mv
para cada archivo existente y uno touch
al final para crear los archivos nuevos. (El toque podría reemplazarse por un bucle que crea los archivos usando la redirección para reducir la cantidad de procesos externos en 1).
#!/bin/bash
shopt -s nullglob # Reduce the number of things we have to work with
# get a list of the files we want to work with.
files=( *.log *.log.[1-9] *.log.[1-9][0-9] )
# reverse the list into rfiles, getting rid of non-file things
rfiles=()
for ((i=${#files[@]}-1;i>=0;i--)) ; do
if [ -f "${files[i]}" ] ; then
rfiles+=("${files[i]}")
fi
done
# exit early if there is nothing to do
if [ ${#rfiles[@]} -eq 0 ] ; then
exit 0
fi
# an array of the files we need to create
typeset -A newfiles
# Loop over the reversed file list
for f in "${rfiles[@]}"; do
# Get everything up to the last "log"
baseName=${f%log*}log
# Remove up to the last "log" and then the optional "."
currentFileNum=${f#"$baseName"}
currentFileNum=${currentFileNum#.}
mv -v "$f" "$baseName.$((currentFileNum+1))"
# record the name to make the new files
newfiles[$baseName]=1
done
# Create all the needed new files, using the names stored in the array
touch "${!newfiles[@]}"
El orden en que hace las cosas es diferente al producido por la solución de @Haxiel, esto mueve primero todos los archivos con números de 2 dígitos, luego todos los archivos con números de un solo dígito y finalmente los archivos que terminan en ".log", en lugar de procesar todos los archivos con las mismas primeras partes juntos.
La pregunta original decía que hay menos de 1000 archivos y menos de 21 versiones por archivo. No dijo qué hacer si hay más que este número. Esta solución admite hasta 100 versiones por archivo y se puede ampliar a 1000 o más simplemente ampliando el patrón.
La cantidad de archivos está limitada por la cantidad de memoria disponible para bash.
Creo que esta es una mejor solución ya que solo intenta manejar archivos que existen en lugar de probar N archivos para cada nombre. Cuando N es pequeño (como 21), esto no importa.