相互追加大型文件而不复制它们


41

每个有大约10G的5个大文件(file1,file2,.. file5),磁盘上剩余的可用空间非常小,我需要将所有这些文件串联在一起。无需保留原始文件,仅保留最后一个文件。

通常串联会cat依次处理文件file2.. file5

cat file2 >> file1 ; rm file2

不幸的是,这种方式需要我没有至少10G的可用空间。有没有一种方法可以串联文件而不进行实际复制,但是以某种方式告诉文件系统,文件1不会在原始文件1结束时结束,而在文件2开始时会继续?

ps。如果重要的话,文件系统是ext4。


2
我希望看到一个解决方案,但我怀疑如果不直接与文件系统搞混就不可能。
凯文

1
为什么需要一个如此大的物理文件?我问是因为也许您可以避免级联,正如当前答案所示,这很麻烦。
liori 2013年

6
@rush:那么这个答案可能会有所帮助:serverfault.com/a/487692/16081
liori 2013年

1
替代设备映射器的方法,效率较低,但更易于实现,并且可以创建可分区的设备,并且可以从远程计算机使用,是使用的“多”模式nbd-server
斯特凡Chazelas

1
当我告诉我我认为这应该很酷时,他们总是称我为愚蠢。
n611x007 2013年

Answers:


19

AFAIK(不幸的是)不可能从一开始就截断文件(这对于标准工具可能是正确的,但对于syscall级别请参见此处)。但是,添加一些复杂性后,您可以使用常规的截断(与稀疏文件一起使用):您可以写入目标文件的末尾而无需在其间写入所有数据。

让我们假设首先两个文件都是5GiB(5120 MiB),并且您想一次移动100 MiB。您执行一个循环,其中包括

  1. 从源文件末尾复制一个块到目标文件末尾(增加消耗的磁盘空间)
  2. 将源文件截短一个块(释放磁盘空间)

    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块大小,生成的文件将成为碎片梦night。


看起来这是连接文件的最可接受的方法。谢谢你的建议。
2013年

3
如果没有稀疏文件支持,则可以逐块地将第二个文件逐个反转,然后删除最后一个块并将其添加到第二个文件中
棘手怪胎

1
我还没有亲自尝试过(尽管我将要尝试),但是seann.herdejurgen.com/resume/samag.com/html/v09/i08/a9_l1.htm是一个声称实现此算法的Perl脚本。
zwol

16

如果您的程序无法处理多个文件,则可以使用命名管道模拟单个文件,而不是将文件分类为一个文件。

mkfifo /tmp/file
cat file* >/tmp/file &
blahblah /tmp/file
rm /tmp/file

正如Hauke所建议的那样,lostup / 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整除,否则您将丢失一些数据。如果是,那么您就很好。我看到他也在下面指出了这一点。


一次读取此文件是个好主意,很遗憾,它没有能力向后/向前跳过fifo,不是吗?
2013年

7
@rush更好的选择是在每个文件上放置一个循环设备,然后将它们组合dmsetup到一个虚拟块设备中(这允许正常的查找操作,但不能追加也不能截断)。如果第一个文件的大小不是512的倍数,则应将不完整的最后一个扇区和第一个字节从第二个文件(总计512)复制到第三个文件。--offset然后需要第二个文件的循环设备。
Hauke Laging 2013年

优雅的解决方案。也向Hauke Laging +1,后者建议了一种解决方法,如果第一个文件的大小不是512的倍数
Olivier Dulac 2013年

9

您必须编写一些内容,以成堆的方式复制数据,这些数据最多与您的可用空间量一样大。它应该像这样工作:

  • 从中读取数据块file2pread()通过在读取之前查找到正确的位置来使用)。
  • 将块追加到file1
  • 用于fcntl(F_FREESP)从释放空间file2
  • 重复

1
我知道...但是我想不出任何不涉及编写代码的方式,而且我认为写自己写的东西总比什么都不写好。我没有想到您从头开始的巧妙技巧!
Celada

您的也一样,如果不从头开始,就无法工作,对吗?
Hauke Laging 2013年

不,它从一开始就起作用,因为fcntl(F_FREESP)它释放了与文件的给定字节范围相关联的空间(这使它稀疏)。
Celada 2013年

太酷了。但是似乎是一个非常新的功能。我的fcntl手册页(2012-04-15)中没有提到它。
Hauke Laging

4
@HaukeLaging F_FREESP是Solaris之一。在Linux(自2.6.38版本开始)上,它是fallocatesyscall 的FALLOC_FL_PUNCH_HOLE标志。较新版本的fallocate实用工具util-linux具有与此接口。
斯特凡Chazelas

0

我知道这比您要求的更能解决问题,但可以解决您的问题(而且不会产生碎片或刮擦):

#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

不幸的是,外部USB磁盘需要物理访问,而nfs需要其他硬件,我什么也没有。还是谢谢你。=)

我认为那是那样。。。Rob Bos的答案似乎是您最好的选择(不要冒着在复制过程中被截断而丢失数据的风险,而且也不会遇到FS限制)
Olivier Dulac
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.