Answers:
在支持该SEEK_HOLE
lseek
标志的系统(和文件系统)上(例如ext4上的Ubuntu 12.04将支持该标志),并假定Linux上的值SEEK_HOLE
是4:
if perl -le 'seek STDIN,0,4;$p=tell STDIN;
seek STDIN,0,2; exit 1 if $p == tell STDIN'< the-file; then
echo the-file is sparse
else
echo the-file is not sparse
fi
该外壳语法为POSIX。里面的不可携带的东西就是perl
那个SEEK_HOLE
。
lseek(SEEK_HOLE)
查找文件中第一个孔的起点,如果找不到孔,则查找文件的结尾。上面我们知道,当lseek(SEEK_HOLE)
我们将我们带到文件末尾(与相同的位置lseek(SEEK_END)
)时,文件并不稀疏。
如果要列出稀疏文件:
find . -type f ! -size 0 -exec perl -le 'for(@ARGV){open(A,"<",$_)or
next;seek A,0,4;$p=tell A;seek A,0,2;print if$p!=tell A;close A}' {} +
GNU find
(版本4.3.3起)必须-printf %S
报告文件的稀疏性。它采用与frostschutz答案相同的方法,因为它采用磁盘使用率与文件大小的比率,因此不能保证报告所有稀疏文件(例如,在文件系统级别进行压缩或漏洞所节省的空间没有报告时)补偿文件系统基础结构的开销或较大的扩展属性),但可以在没有SEEK_HOLE
文件系统或文件系统SEEK_HOLE
未实现的系统上使用。这里使用GNU工具:
find . -type f ! -size 0 -printf '%S:%p\0' |
awk -v RS='\0' -F : '$1 < 1 {sub(/^[^:]*:/, ""); print}'
(请注意,此答案的早期版本在find
表示稀疏性时(例如3.2e-05)无法正常工作。感谢@flashydave的回答引起了我的注意)
find
还应该彻底排除0字节文件?
find -printf '%S'
!:-)
tr
用命令xargs -r0 rm -f
当分配的块数小于文件大小时,文件通常是稀疏的(此处使用的stat
是在Ubuntu上使用的GNU ,但要注意其他系统可能不兼容的实现stat
)。
if [ "$((`stat -c '%b*%B-%s' -- "$file"`))" -lt 0 ]
then
echo "$file" is sparse
else
echo "$file" is not sparse
fi
与find
:的变体(从斯蒂芬偷来的)
find . -type f ! -size 0 -exec bash -c '
for f do
[ "$((`stat -c "%b*%B-%s" -- "$f"`))" -lt 0 ] && printf "%s\n" "$f";
done' {} +
通常,您通常将其放在shell脚本中,然后执行shell脚本。
find . -type f ! -size 0 -exec ./sparsetest.sh {} +
SEEK_HOLE
但是同样存在问题,因为许多平台/文件系统都不支持它。在Linux中,您也可以使用FIEMAP
/ FIBMAP
,但是FIBMAP
特别慢,这似乎不是一个好方法。
for file in *
或中非常简单find
。如果可以测试单个文件,则可以测试所有文件...尽管您必须使用此方法排除目录。
上面的Stephane Chazelas答案没有考虑到一些带有find%S参数的稀疏文件将比率报告为浮点数的事实,例如
9.31323e-09:./somedir/sparsefile.bin
这些可以在
find . -type f ! -size 0 -printf '%S:%p\0' |
sed -zn '/^\(0[^:]*:\)\|\([0-9.]\+e-.*:\)/p' |
tr '\0' '\n'
我在尝试找出文件中孔的位置时写的一个简短脚本:
#!/usr/bin/python3
import os
import sys
import errno
def report(fname):
fd = os.open(fname, os.O_RDONLY)
len = os.lseek(fd, 0, os.SEEK_END)
offset = 0
while offset < len:
start = os.lseek(fd, offset, os.SEEK_HOLE)
if start == len:
break
try:
offset = os.lseek(fd, start, os.SEEK_DATA)
except OSError as e:
if e.errno == errno.ENXIO:
offset = len
else:
raise
print(f'found hole between 0x{start:08X} and 0x{offset:08X} ({offset - start} bytes)')
if __name__ == '__main__':
for name in sys.argv[1:]:
report(name)
打印的内容如下:
$ echo -n 'a' >zeros; truncate -s $((4096*4)) zeros; test/report-holes.py zeros
found hole between 0x00001000 and 0x00004000 (12288 bytes)