我需要一些有关 shell 代码的帮助。现在我有这个代码:
find $dirname -type f -exec md5sum '{}' ';' | sort | uniq --all-repeated=separate -w 33 | cut -c 35-
此代码在给定目录中查找重复文件(具有相同内容)。我需要做的是更新它 - 找出最新(按日期)修改的文件(从重复文件列表中),打印该文件名并提供在终端中删除该文件的机会。
我需要一些有关 shell 代码的帮助。现在我有这个代码:
find $dirname -type f -exec md5sum '{}' ';' | sort | uniq --all-repeated=separate -w 33 | cut -c 35-
此代码在给定目录中查找重复文件(具有相同内容)。我需要做的是更新它 - 找出最新(按日期)修改的文件(从重复文件列表中),打印该文件名并提供在终端中删除该文件的机会。
这是一个用bash实现的“幼稚”解决方案(当然,除了两个外部命令:md5sum
,并且stat
仅用于用户舒适,它不是算法的一部分)。这个东西实现了 100% Bash 快速排序(我有点自豪):
#!/bin/bash
# Finds similar (based on md5sum) files (recursively) in given
# directory. If several files with same md5sum are found, sort
# them by modified (most recent first) and prompt user for deletion
# of the oldest
die() {
printf >&2 '%s\n' "$@"
exit 1
}
quicksort_files_by_mod_date() {
if ((!$#)); then
qs_ret=()
return
fi
# the return array is qs_ret
local first=$1
shift
local newers=()
local olders=()
qs_ret=()
for i in "$@"; do
if [[ $i -nt $first ]]; then
newers+=( "$i" )
else
olders+=( "$i" )
fi
done
quicksort_files_by_mod_date "${newers[@]}"
newers=( "${qs_ret[@]}" )
quicksort_files_by_mod_date "${olders[@]}"
olders=( "${qs_ret[@]}" )
qs_ret=( "${newers[@]}" "$first" "${olders[@]}" )
}
[[ -n $1 ]] || die "Must give an argument"
[[ -d $1 ]] || die "Argument must be a directory"
dirname=$1
shopt -s nullglob
shopt -s globstar
declare -A files
declare -A hashes
for file in "$dirname"/**; do
[[ -f $file ]] || continue
read md5sum _ < <(md5sum -- "$file")
files[$file]=$md5sum
((hashes[$md5sum]+=1))
done
has_found=0
for hash in "${!hashes[@]}"; do
((hashes[$hash]>1)) || continue
files_with_same_md5sum=()
for file in "${!files[@]}"; do
[[ ${files[$file]} = $hash ]] || continue
files_with_same_md5sum+=( "$file" )
done
has_found=1
echo "Found ${hashes[$hash]} files with md5sum=$hash, sorted by modified (most recent first):"
# sort them by modified date (using quicksort :p)
quicksort_files_by_mod_date "${files_with_same_md5sum[@]}"
for file in "${qs_ret[@]}"; do
printf " %s %s\n" "$(stat --printf '%y' -- "$file")" "$file"
done
read -p "Do you want to remove the oldest? [yn] " answer
if [[ ${answer,,} = y ]]; then
echo rm -fv -- "${qs_ret[@]:1}"
fi
done
if((!has_found)); then
echo "Didn't find any similar files in directory \`$dirname'. Yay."
fi
我猜剧本是不言自明的(你可以像故事一样阅读它)。它使用我所知道的最佳实践,并且对于文件名中的任何愚蠢字符(例如,空格、换行符、以连字符开头的文件名、以换行符结尾的文件名等)都是 100% 安全的。
它使用 bash 的 glob,所以如果你有一个臃肿的目录树,它可能会有点慢。
有一些错误检查,但很多都丢失了,所以不要在生产中按原样使用!(添加这些是微不足道但相当乏味的任务)。
算法如下:扫描给定目录树中的每个文件;对于每个文件,将计算其 md5sum 并存储在关联数组中:
files
键是文件名和值 md5sums。hashes
带有键的哈希值和值是文件的数量,其中 md5sum 是键。完成此操作后,我们将扫描所有找到的 md5sum,仅选择与多个文件对应的那些,然后选择所有具有此 md5sum 的文件,然后按修改日期对其进行快速排序,并提示用户。
没有找到副本时的甜蜜效果:脚本很好地通知用户。
我不会说它是最有效的做事方式(在 Perl 中可能会更好),但它真的很有趣,非常容易阅读和遵循,而且你可以通过研究它潜在地学到很多东西!
它使用了一些仅在 bash 版本≥ 4 中的 bashism 和功能
希望这可以帮助!
评论。如果您的系统date
上有-r
开关,您可以将stat
命令替换为:
date -r "$file"
评论。我离开了echo
前面rm
。如果您对脚本的行为方式感到满意,请将其删除。然后,您将拥有一个使用 3 个外部命令的脚本:)
。
在纯 bash 中这样做有点尴尬,用 perl 或 python 编写会容易得多。
此外,如果您希望使用 bash 单线来执行此操作,这可能是可行的,但我真的不知道该怎么做。
Anyhoo,如果您真的想要下面的纯 bash 解决方案,请尝试按照您的描述进行操作。
请注意:
这是代码:
#!/bin/bash
buffer=''
function process {
if test -n "$buffer"
then
nbFiles=$(printf "%s" "$buffer" | wc -l)
echo "================================================================================="
echo "The following $nbFiles files are byte identical and sorted from oldest to newest:"
ls -lt -c -r $buffer
lastFile=$(ls -lt -c -r $buffer | tail -1)
echo
while true
do
read -u 1 -p "Do you wish to delete the last file $lastFile (y/n/q)? " answer
case $answer in
[Yy]* ) echo rm $lastFile; break;;
[Nn]* ) echo skipping; break;;
[Qq]* ) exit;;
* ) echo "please answer yes, no or quit";;
esac
done
echo
fi
}
find . -type f -exec md5sum '{}' ';' |
sort |
uniq --all-repeated=separate -w 33 |
cut -c 35- |
while read -r line
do
if test -z "$line"
then
process
buffer=''
else
buffer=$(printf "%s\n%s" "$buffer" "$line")
fi
done
process
echo "done"