Додайте величезні файли один до одного, не копіюючи їх


41

На диску залишається 5 величезних файлів (file1, file2, .. file5) приблизно 10 Г кожен і надзвичайно мало вільного місця, і мені потрібно об'єднати всі ці файли в один. Немає необхідності зберігати оригінальні файли, лише остаточні.

Звичайне з'єднання відбувається catв послідовності для файлів file2.. file5:

cat file2 >> file1 ; rm file2

На жаль, цей спосіб вимагає принаймні 10G вільного місця у мене немає. Чи є спосіб об'єднати файли без фактичного їх копіювання, але скажіть файловій системі якось, що файл1 не закінчується на початковому кінці file1 і продовжується при запуску file2?

пс. файлова система є ext4, якщо це має значення.


2
Мені було б цікаво побачити рішення, але я підозрюю, що це неможливо без безпосереднього водіння з файловою системою.
Кевін

1
Чому потрібно мати єдиний настільки великий фізичний файл? Я запитую, тому що, можливо, ви можете уникнути об'єднання, що, як показують нинішні відповіді, є досить набридливим.
liori

6
@rush: тоді ця відповідь може допомогти: serverfault.com/a/487692/16081
liori

1
Альтернативою пристрою-картографом, менш ефективним, але простішим у впровадженні та результатом використання пристрою, що розділяється, і його можна використовувати на віддаленій машині, є використання режиму "мульти" nbd-server.
Стефан Шазелас

1
Вони завжди називають мене дурним, коли я кажу, що я думаю, що це має бути круто.
n611x007

Відповіді:


19

AFAIK (на жаль) неможливо врізати файл з самого початку (це може бути справедливо для стандартних інструментів, але для рівня syscall див. Тут ). Але додавши деяку складність, ви можете використовувати звичайне обрізання (разом із розрідженими файлами): Ви можете записувати до кінця цільового файлу, не записуючи між собою всі дані.

Припустимо, спочатку обидва файли мають рівно 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
якщо немає розрідженої підтримки файлів, тоді ви можете блочно повернути другий файл на місці, а потім просто видалити останній блок і додати його до другого файлу
храповика виродка

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

Як підказує Хоук, 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, або ви втратите деякі дані. Якщо так, то ви добрі. Я бачу, він також зазначив, що нижче.


Чудова ідея прочитати цей файл один раз, на жаль, він не має можливості перестрибувати фіфо назад або вперед, чи не так?
пік

7
@rush Вищою альтернативою може бути встановлення циклічного пристрою на кожен файл та об'єднання їх через dmsetupвіртуальний блок пристрою (який дозволяє нормально шукати операції, але ні додавати, ні скорочувати). Якщо розмір першого файлу не кратний 512, то слід скопіювати неповний останній сектор та перші байти з другого файлу (у сумі 512) у третій файл. Тоді знадобиться пристрій циклу для другого файлу --offset.
Hauke ​​Laging

елегантні рішення. +1 також Хоуке Лагінгу, які пропонують спосіб вирішити проблему, якщо розмір першого файлу не кратний 512
Олів'є Дулак

9

Вам доведеться записати щось, що копіює дані в пучки, які є не менше, ніж кількість вільного місця. Це має працювати так:

  • Прочитайте блок даних із file2(використовуючи pread()пошук перед прочитаним у потрібне місце).
  • Додайте блок до file1.
  • Використовуйте fcntl(F_FREESP)для розміщення місця з file2.
  • Повторіть

1
Я знаю ... але я не міг придумати жодного способу, який би не передбачав написання коду, і я зрозумів, що писати те, що я написав, краще, ніж писати нічого. Я не думав про твій розумний трюк починати з кінця!
Селада

Ваші теж не спрацювали, не починаючи з кінця, чи не так?
Hauke ​​Laging

Ні, він працює з самого початку, через fcntl(F_FREESP)що звільняє простір, пов'язаний із заданим байтовим діапазоном файлу (він робить його рідким).
Селада

Це досить круто. Але, здається, це зовсім нова особливість. Це не згадується на моїй fcntlсторінці чоловіка (2012-04-15).
Hauke ​​Laging

4
@HaukeLaging F_FREESP - це Solaris. В Linux (з 2.6.38) це прапор fallocateFALLOC_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)
Олів'є Дулак
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.