
Мне дали задачу, и мое решение прошло первоначальные тестовые задания, но при отправке не прошло 50% тестов.
Проблема: каталог содержит несколько файлов и папок, некоторые из этих файлов являются различными типами журналов: error.log, error.log.1, error.log.2, access.log.1, access.log.2 и т. д. Содержимое этих файлов сопоставляется со следующим днем, поэтому «cat error.log.1» имеет «журналы дня 2» и т. д.
Задача состоит в том, чтобы увеличить номер только в конце логов, а остальное содержимое каталога оставить неизменным. Также создайте пустой файл для каждого типа логов.
Например:
./
example_dir
example2_dir
error.log
error.log.1
info.log.20
access.log.1
readme.txt
Скрипт меняет каталог на:
./
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)
Условия: # Файлов в каталоге < 1000, Макс # Файлов каждого типа < 21
Мое решение:
#!/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
Там не показаны тестовые случаи, которые я провалил, поэтому я не совсем уверен, как решить эту проблему лучше.
решение1
Это было забавное упражнение, и вот мое решение.
#/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
Переменная log_names использует find
команду для получения списка файлов журнала. Затем я применяю подстановку строки, чтобы удалить числовой суффикс. После этого я сортирую и удаляю дубликаты.
На этом этапе я получаю список уникальных имен файлов журнала в каталоге: ./access.log ./error.log ./info.log
.
Затем я обрабатываю каждое имя по очереди, используя for
цикл.
Теперь для каждого файла нам сообщили, что максимально возможное число — 20. Мы начинаем с этого и используем until
цикл для обратного отсчета.
Логика mv
проста: если существует «filname.number», переместить его в «filename.(number+1)».
Когда until
цикл завершится (i = 0), у нас может остаться один не повернутый файл — тот, у которого нет числового суффикса. Если это так, переместите его в filename.1.
Последний шаг — создание пустого файла с расширением touch
.
Пример исполнения:
$ 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
решение2
@Haxiel опубликовал решение. Это похоже на то, что я имел в виду, на то, что я описал как "самое прямолинейное". Я бы использовал цикл, for
а не until
цикл.
Это то, что использует почти минимальное количество внешних процессов, по одному mv
для каждого существующего файла и один touch
в конце для создания новых файлов. (Касание можно заменить циклом, создающим файлы с использованием перенаправления, чтобы уменьшить количество внешних процессов на 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[@]}"
Порядок выполнения действий отличается от порядка, созданного решением @Haxiel: оно сначала перемещает все файлы с двузначными номерами, затем все файлы с однозначными номерами и, наконец, файлы, заканчивающиеся на «.log», вместо того, чтобы обрабатывать все файлы с одинаковыми первыми частями вместе.
В исходном вопросе говорилось, что файлов менее 1000 и версий на файл менее 21. Что делать, если их больше, чем это число, не говорилось. Это решение рассчитано на 100 версий на файл и может быть расширено до 1000 или более, просто расширив шаблон.
Количество файлов ограничено объемом памяти, доступной bash.
Я считаю, что это лучшее решение, поскольку оно пытается работать только с существующими файлами, а не пытается перебрать N файлов для каждого имени. Когда N небольшое (например, 21), это не имеет значения.