我遇到了一个问题,我的解决方案通过了初始测试用例,但提交时失败了 50%。
问题:一个目录包含多个文件和文件夹,其中有些文件是不同类型的日志; error.log、error.log.1、error.log.2、access.log.1、access.log.2 ..etc 这些文件的内容映射到第二天,因此“cat error.log.1”有“第二天日志”..等
任务是仅增加日志末尾的数字,并保持目录的其余内容不变。另外,为每种日志类型创建一个空文件。
例如:
./
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, 每个类型的最大 #Files < 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 的解决方案生成的顺序不同,这首先移动所有具有 2 位数字的文件,然后移动所有具有单位数字的文件,最后移动以“.log”结尾的文件,而不是处理所有具有相同第一部分的文件放在一起。
最初的问题说文件少于 1000 个,每个文件少于 21 个版本。它没有说如果超过这个数字该怎么办。该解决方案可满足每个文件最多 100 个版本的需求,并且只需扩展模式即可扩展到 1000 个或更多。
文件数量受到 bash 可用内存量的限制。
我相信这是一个更好的解决方案,因为它只尝试处理存在的文件,而不是为每个名称尝试 N 个文件。当 N 很小(比如 21)时,这并不重要。