有 5 个大文件(文件 1、文件 2、.. 文件 5),每个文件大约 10G,磁盘上剩余的可用空间极低,我需要将所有这些文件连接成一个。无需保留原始文件,只需保留最终文件。
cat
通常的串联是按文件顺序进行的file2
.. file5
:
cat file2 >> file1 ; rm file2
不幸的是,这种方式需要至少 10G 的可用空间,我没有。有没有办法在不实际复制文件的情况下连接文件,但以某种方式告诉文件系统 file1 不会在原始 file1 结束处结束并在 file2 开始处继续?
附注如果重要的话,文件系统是 ext4。
答案1
AFAIK(不幸的是)不可能从头开始截断文件(这对于标准工具可能是正确的,但对于系统调用级别看这里)。但增加一些复杂性后,您可以使用正常截断(与稀疏文件一起):您可以写入目标文件的末尾,而无需写入其间的所有数据。
首先,我们假设这两个文件的大小正好是 5GiB (5120 MiB),并且您希望一次移动 100 MiB。您执行一个循环,其中包含
- 从源文件末尾复制一个块到目标文件末尾(增加消耗的磁盘空间)
将源文件截断一块(释放磁盘空间)
for((i=5119;i>=0;i--)); do dd if=sourcefile of=targetfile bs=1M skip="$i" seek="$i" count=1 dd if=/dev/zero of=sourcefile bs=1M count=0 seek="$i" done
但请先尝试使用较小的测试文件...
可能这些文件的大小既不是相同的大小,也不是块大小的倍数。在这种情况下,偏移量的计算变得更加复杂。然后seek_bytes
应该skip_bytes
使用。
如果这是您想要的方式,但需要有关详细信息的帮助,请再次询问。
警告
根据dd
块大小,生成的文件将是一场碎片噩梦。
答案2
如果您的程序无法处理多个文件,则可以使用命名管道模拟单个文件,而不是将这些文件合并到一个文件中。
mkfifo /tmp/file
cat file* >/tmp/file &
blahblah /tmp/file
rm /tmp/file
正如 Hauke 所建议的,losetup/dmsetup 也可以工作。一个快速实验;我创建了“file1..file4”,经过一番努力,我做到了:
for i in file*;do losetup -f ~/$i;done
numchunks=3
for i in `seq 0 $numchunks`; do
sizeinsectors=$((`ls -l file$i | awk '{print $5}'`/512))
startsector=$(($i*$sizeinsectors))
echo "$startsector $sizeinsectors linear /dev/loop$i 0"
done | dmsetup create joined
然后,/dev/dm-0 包含一个虚拟块设备,其中包含您的文件作为内容。
我还没有测试过这个好。
另一项编辑:文件大小必须能被 512 整除,否则您将丢失一些数据。如果是的话,那你就很好了。我看到他也在下面指出了这一点。
答案3
您必须编写一些东西来复制数据,这些数据最多与您拥有的可用空间量一样大。它应该像这样工作:
- 从中读取数据块
file2
(pread()
在读取到正确位置之前使用通过查找)。 - 将块附加到
file1
. - 用于
fcntl(F_FREESP)
取消分配 中的空间file2
。 - 重复
答案4
我知道这比您所要求的更多是一种解决方法,但它会解决您的问题(并且几乎没有碎片或麻烦):
#step 1
mount /path/to/... /the/new/fs #mount a new filesystem (from NFS? or an external usb disk?)
进而
#step 2:
cat file* > /the/new/fs/fullfile
或者,如果您认为压缩会有帮助:
#step 2 (alternate):
cat file* | gzip -c - > /the/new/fs/fullfile.gz
然后(并且只有那时),最后
#step 3:
rm file*
mv /the/new/fs/fullfile . #of fullfile.gz if you compressed it