Добавлять огромные файлы друг к другу, не копируя их

41

Есть 5 огромных файлов (file1, file2, .. file5) по 10G каждый и на диске осталось очень мало свободного места, и мне нужно объединить все эти файлы в один. Сохранять оригинальные файлы не нужно, только конечный.

Обычная конкатенация происходит catпоследовательно для файлов file2.. file5:

cat file2 >> file1 ; rm file2

К сожалению, этот способ требует как минимум 10G свободного места, которого у меня нет. Есть ли способ объединить файлы без фактического копирования, но как-то сказать файловой системе, что file1 не заканчивается в конце file1 и продолжается в начале file2?

пс. файловая система ext4, если это имеет значение.

порыв
источник
2
Мне было бы интересно увидеть решение, но я подозреваю, что это невозможно без непосредственного взаимодействия с файловой системой.
Кевин
1
Зачем вам нужен один такой большой физический файл? Я спрашиваю, потому что, возможно, вы можете избежать объединения, что, как показывают текущие ответы, довольно утомительно.
Лиори
6
@rush: тогда этот ответ может помочь: serverfault.com/a/487692/16081
liori
1
Альтернатива устройству-сопоставителю, менее эффективная, но более легкая в реализации и приводящая к разделению устройства и может использоваться с удаленной машины, заключается в использовании «мульти» режима работы nbd-server.
Стефан Шазелас
1
Они всегда называют меня глупым, когда я говорю, что думаю, что это должно быть круто.
n611x007

Ответы:

19

AFAIK (к сожалению) невозможно обрезать файл с самого начала (это может быть верно для стандартных инструментов, но для уровня системного вызова см. Здесь ). Но с добавлением некоторой сложности вы можете использовать обычное усечение (вместе с разреженными файлами): вы можете записать в конец целевого файла, не записав все данные между ними.

Предположим сначала, что оба файла имеют размер 5 ГБ (5120 МБ) и что вы хотите переместить 100 МБ за раз. Вы выполняете цикл, который состоит из

  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размера блока результирующий файл будет кошмаром фрагментации.

Хауке Лагинг
источник
Похоже, это наиболее приемлемый способ объединения файлов. Спасибо за совет.
Раш
3
если нет поддержки разреженных файлов, то вы можете блочно перевернуть второй файл на месте, а затем просто удалить последний блок и добавить его во второй файл
ratchet freak
1
Я сам не пробовал (хотя собираюсь), но seann.herdejurgen.com/resume/samag.com/html/v09/i08/a9_l1.htm - это скрипт на Perl, который претендует на реализацию этого алгоритма.
Звол
16

Вместо того, чтобы объединять файлы в один файл, возможно, имитируйте один файл с именованным каналом, если ваша программа не может обрабатывать несколько файлов.

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

Как предполагает Хауке, losttup / 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 назад / вперед, не так ли?
Раш
7
@rush Лучшей альтернативой может быть наложение петлевого устройства на каждый файл и объединение их через dmsetupвиртуальное блочное устройство (которое позволяет выполнять обычные операции поиска, но не добавляет и не усекает). Если размер первого файла не кратен 512, то вы должны скопировать неполный последний сектор и первые байты из второго файла (в сумме 512) в третий файл. Устройство цикла для второго файла потребуется --offsetтогда.
Хауке Лагинг
элегантные решения. +1 также Хауке Лагингу, который предлагает способ обойти проблему, если размер первого файла (ов) не кратен 512
Оливье Дюлак
9

Вам нужно будет написать что-то, что копирует данные в пакеты, которые не превышают объем свободного места, который у вас есть. Это должно работать так:

  • Считать блок данных file2(используя pread()поиск до чтения в правильном месте).
  • Добавить блок в file1.
  • Используйте, fcntl(F_FREESP)чтобы освободить место от file2.
  • Повторение
Celada
источник
1
Я знаю ... но я не мог придумать ни одного способа, который бы не предусматривал написание кода, и я подумал, что писать то, что я написал, было лучше, чем ничего не писать. Я не думал о твоей хитрой уловке начинать с конца!
Селада
Ваша тоже не сработает, не начав с конца, не так ли?
Хауке Лагинг
Нет, он работает с самого начала, из-за fcntl(F_FREESP)чего освобождает пространство, связанное с заданным диапазоном байтов файла (это делает его разреженным).
Селада
Это круто. Но, похоже, это очень новая функция. Это не упомянуто в моей fcntlсправочной странице (2012-04-15).
Хауке Лагинг
4
@HaukeLaging F_FREESP - это Solaris. В Linux (начиная с версии 2.6.38) это флаг fallocateсистемного вызова FALLOC_FL_PUNCH_HOLE. Более новые версии утилиты fallocate от util-linuxимеют интерфейс к этому.
Стефан Шазелас
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 требует дополнительного оборудования, и у меня ничего нет. В любом случае, спасибо. =)
Раш
Я думал, что так и будет ... Ответ Роба Боса - это то, что кажется вам лучшим вариантом (без риска потери данных из-за обрезания при копировании и без нарушения ограничений FS)
Оливье Дюлак