我有一个包含每日快照文件夹的备份。为了节省空间,不同快照中的相同文件通过硬链接(由 rsync 生成)进行重复数据删除。
当空间不足时,一种选择是删除旧快照。但由于硬链接,很难计算出删除给定快照会获得多少空间。
我能想到的一个选择是du -s
首先在所有快照文件夹上使用,然后在除我可能删除的文件夹之外的所有文件夹上使用,差异将为我提供预期的获得空间。然而,这非常麻烦,当我试图找到合适的快照进行删除时,必须重复进行。
有更容易的方法吗?
尝试并思考答案后斯蒂芬·查泽拉斯和德罗伯特,我意识到我的问题不够精确。这是更精确的尝试:
我有一组目录(“快照”),其中包含与另一个快照中的文件部分存储相同(硬链接)的文件。我正在寻找一种解决方案,为我提供快照列表以及其中的文件占用的每个已用磁盘存储量,但没有该存储也被另一个快照中的文件使用。我想允许每个快照中存在硬链接的可能性。
我的想法是,我可以查看该列表来决定当空间不足时应该删除哪些快照,这是删除所获得的存储空间与快照价值(例如基于年龄)之间的权衡。
答案1
您可以使用 GNU 手动完成find
:
find snapshot-dir -type d -printf '1 %b\n' -o -printf '%n %b %i\n' |
awk '$1 == 1 || ++c[$3] == $1 {t+=$2;delete c[$3]}
END{print t*512}'
它计算的是在快照目录中找到的所有链接都找到后,其链接计数将降至 0 的文件的磁盘使用情况。
find
印刷:
1 <disk-usage>
对于目录<link-count> <disk-usage> <inode-number>
对于其他类型的文件。
我们假设目录的链接计数始终为 1,因为实际上并非如此,这是因为条目的原因..
,并且find
不列出这些条目,并且目录通常没有其他硬链接。
从该输出中,awk
计算链接计数为 1 的条目的磁盘使用情况以及它所看到的<link-count>
次数的 inode(即所有硬链接都在当前目录中的条目,因此,就像带有链接的条目一样) -count of one 将在目录树被删除后回收其空间)。
您还可以使用来find snapshot-dir1 snapshot-dir2
找出如果两个目录都被删除,将回收多少磁盘空间(如果在两个目录中都找到并且仅在这些目录中找到文件,则这可能大于单独占用的两个目录的空间总和)快照)。
如果您想了解每次删除快照目录后会节省多少空间(以累积方式),您可以这样做:
find snapshot-dir* \( -path '*/*' -o -printf "%p:\n" \) \
-type d -printf '1 %b\n' -o -printf '%n %b %i\n' |
awk '/:$/ {if (NR>1) print t*512; printf "%s ", $0; next}
$1 == 1 || ++c[$3] == $1 {t+=$2;delete c[$3]}
END{print t*512}'
它按词汇顺序处理快照列表。如果您以不同的顺序处理它,则可能会为您提供不同的数字,除了最后一个数字(当所有快照都被删除时)。
请参阅numfmt
使数字更具可读性。
假设所有文件都位于同一文件系统上。如果没有,您可以替换%i
为%D:%i
(如果它们不全部位于同一文件系统上,这意味着您在那里有一个无论如何都无法删除的安装点)。
答案2
如果您的文件名不包含模式字符或换行符,您可以使用find
+du
的排除功能来执行此操作:
find -links +1 -type f \
| cut -d/ -f2- \
| du --exclude-from=- -s *
该位获取硬链接计数大于 1 ( )find
的所有文件 ( ) 。打印出前导查找内容的修剪部分。然后询问每个目录的磁盘使用情况,不包括具有多个链接的所有文件。当然,一旦您删除了快照,现在可能存在只有一个链接的文件,而之前有两个链接 - 因此每隔几次删除,您确实应该重新运行它。-type f
-links +1
cut
./
du
如果它需要使用任意文件名,则需要更多脚本来替换du
(这些是 shell 模式,因此不可能进行转义)。
此外,正如 Stéphane Chazelas 指出的那样,如果一个快照内部存在硬链接(文件的所有名称都驻留在单个快照中,而不是快照之间的硬链接),这些文件将从总数中排除(即使删除快照会恢复该空间)。
答案3
自从我写下这个答案以来,Stéphane Chazelas 就让我相信他的答案一直都是正确的。我留下我的答案,包括代码,因为它也运行良好,并且提供了一些漂亮的打印。它的输出如下所示:
total unique
--T---G---M---k---B --T---G---M---k---B
91,044,435,456 665,754,624 back-2018-03-01T06:00:01
91,160,015,360 625,541,632 back-2018-04-01T06:00:01
91,235,970,560 581,360,640 back-2018-05-01T06:00:01
91,474,846,208 897,665,536 back-2018-06-01T06:00:01
91,428,597,760 668,853,760 back-2018-07-01T06:00:01
91,602,767,360 660,594,176 back-2018-08-01T06:00:01
91,062,218,752 1,094,236,160 back-2018-09-01T06:00:01
230,810,647,552 50,314,291,712 back-2018-11-01T06:00:01
220,587,811,328 256,036,352 back-2018-11-12T06:00:01
220,605,425,664 267,876,352 back-2018-11-13T06:00:01
220,608,163,328 268,711,424 back-2018-11-14T06:00:01
220,882,714,112 272,000,000 back-2018-11-15T06:00:01
220,882,118,656 263,202,304 back-2018-11-16T06:00:01
220,882,081,792 263,165,440 back-2018-11-17T06:00:01
220,894,113,280 312,208,896 back-2018-11-18T06:00:01
由于我对这两个答案都不是 100% 满意(截至 2018 年 11 月 18 日)——尽管我从这两个答案中学到了——我创建了自己的工具并将其发布在这里。
如同斯蒂芬·查泽拉斯的答案,它用于find
获取索引节点列表和关联的文件/目录大小,但不依赖于“最多一个链接”启发式。相反,它为每个输入目录创建一个唯一的 inode 列表(不是文件/目录!),过滤掉其他目录中的 inode,并对剩余 inode 的大小求和。这样它就可以解释每个输入目录中可能存在的硬链接。作为副作用,它忽略来自输入目录集外部的可能的硬链接。
使用的 bash 外部工具:find
, xargs
, mktemp
, sort
, tput
, awk
, tr
, numfmt
, touch
, cat
, comm
, rm
。我知道,它并不完全是轻量级的,但它完全符合我的要求。我在这里分享一下,以防其他人有类似的需求。
如果有什么可以做得更高效或者万无一失的,欢迎评论!我绝对不是一个 bash 大师。
要使用它,请将以下代码保存到脚本文件中duu.sh
。第一个注释块中包含简短的使用说明。
#!/bin/bash
# duu
#
# disk usage unique to a directory within a set of directories
#
# Call with a list of directory names. If called without arguments,
# it operates on the subdirectories of the current directory.
# no arguments: call itself with subdirectories of .
if [ "$#" -eq 0 ]
then
exec find . -maxdepth 1 -type d ! -name . -printf '%P\0' | sort -z \
| xargs -r --null "$0"
exit
fi
# create temporary directory
T=`mktemp -d`
# array of directory names
dirs=("$@")
# number of directories
n="$#"
# for each directory, create list of (unique) inodes with size
for i in $(seq 1 $n)
do
echo -n "reading $i/$n: ${dirs[$i - 1]} "
find "${dirs[$i - 1]}" -printf "%i\t%b\n" | sort -u > "$T/$i"
# find %b: "The amount of disk space used for this file in 512-byte blocks."
echo -ne "\r"
tput el
done
# print header
echo " total unique"
echo "--T---G---M---k---B --T---G---M---k---B"
# for each directory
for i in $(seq 1 $n)
do
# compute and print total size
# sum block sizes and multiply by 512
awk '{s += $2} END{printf "%.0f", s * 512}' "$T/$i" \
| tr -d '\n' \
| numfmt --grouping --padding 19
echo -n " "
# compute and print unique size
# create list of (unique) inodes in the other directories
touch "$T/o$i"
for j in $(seq 1 $n)
do
if [ "$j" -ne "$i" ]
then
cat "$T/$j" >> "$T/o$i"
fi
done
sort -o "$T/o$i" -u "$T/o$i"
# create list of (unique) inodes that are in this but not in the other directories
comm -23 "$T/$i" "$T/o$i" > "$T/u$i"
# sum block sizes and multiply by 512
awk '{s += $2} END{printf "%.0f", s * 512}' "$T/u$i" \
| tr -d '\n' \
| numfmt --grouping --padding 19
# append directory name
echo " ${dirs[$i - 1]}"
done
# remove temporary files
rm -rf "$T"