
Recebi um problema e minha solução passou nos casos de teste iniciais, mas falhou 50% no envio.
O problema: um diretório contém vários arquivos e pastas; alguns desses arquivos são diferentes tipos de logs; error.log, error.log.1, error.log.2, access.log.1, access.log.2 ..etc O conteúdo desses arquivos é mapeado para o dia seguinte, então 'cat error.log.1' tem 'logs do dia 2' .. etc
A tarefa é incrementar o número apenas no final dos logs e deixar o restante do conteúdo do diretório inalterado. Além disso, crie um arquivo vazio para cada tipo de log.
Por exemplo:
./
example_dir
example2_dir
error.log
error.log.1
info.log.20
access.log.1
readme.txt
O script altera o diretório para:
./
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)
Condições: # Arquivos no diretório <1000, Max #Arquivos por tipo <21
Minha solução:
#!/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
Ele não mostrou os casos de teste nos quais falhei, então não tenho certeza de como resolver isso melhor.
Responder1
Este foi um exercício divertido, então aqui está minha solução.
#/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
A variável log_names usa um find
comando para obter a lista de arquivos de log. Em seguida, aplico uma substituição de string para remover o sufixo numérico. Depois disso, classifico e removo as duplicatas.
Neste ponto, recebo uma lista de nomes exclusivos de arquivos de log no diretório: ./access.log ./error.log ./info.log
.
Em seguida, processo cada nome por vez usando um for
loop.
Agora, para cada arquivo, fomos informados de que o número máximo possível é 20. Começamos aí e usamos um until
loop para fazer a contagem regressiva.
A mv
lógica é simples: se 'filname.number' existir, mova-o para 'filename.(number+1)'.
Quando o until
loop terminar (i = 0), podemos ter um arquivo não girado restante - aquele sem o sufixo numérico. Se isso acontecer, mova-o para filename.1.
A última etapa é criar um arquivo vazio com a extensão touch
.
Execução de amostra:
$ 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
Responder2
@Haxiel postou uma solução. Isso é semelhante ao que eu tinha em mente e ao que descrevi como "mais direto". Eu teria usado um for
loop em vez do until
loop.
Isso é algo que utiliza quase o mínimo de processos externos, um mv
para cada arquivo existente e outro touch
no final para criar os novos arquivos. (O toque pode ser substituído por um loop criando os arquivos usando redirecionamento para reduzir o número de processos externos em 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[@]}"
A ordem em que isso faz as coisas é diferente daquela produzida pela solução @Haxiel, que move primeiro todos os arquivos com números de 2 dígitos, depois todos os arquivos com números de um dígito e, finalmente, os arquivos que terminam em ".log", em vez de processar todos os arquivos com as mesmas primeiras partes juntos.
A pergunta original dizia que existem menos de 1.000 arquivos e menos de 21 versões por arquivo. Não disse o que fazer se houver mais do que esse número. Esta solução atende até 100 versões por arquivo e pode ser estendida para 1.000 ou mais apenas estendendo o padrão.
O número de arquivos é limitado pela quantidade de memória disponível para o bash.
Acredito que esta seja uma solução melhor, pois tenta apenas lidar com arquivos existentes, em vez de tentar N arquivos para cada nome. Quando N é pequeno (como 21), isso não importa.