Dołącz do siebie ogromne pliki bez ich kopiowania

41

Na dysku znajduje się 5 ogromnych plików (plik 1, plik 2, ... plik 5) o wielkości około 10 GB i bardzo mało wolnego miejsca na dysku i muszę połączyć wszystkie te pliki w jeden. Nie ma potrzeby przechowywania oryginalnych plików, tylko ostatni.

Zwykle konkatenacja odbywa się catdla plików file2.. file5:

cat file2 >> file1 ; rm file2

Niestety ten sposób wymaga co najmniej 10G wolnego miejsca, którego nie mam. Czy istnieje sposób na konkatenację plików bez faktycznego kopiowania, ale powiedzieć systemowi plików, że plik1 nie kończy się na oryginalnym końcu pliku 1 i kontynuuje na początku pliku 2?

ps. system plików to ext4, jeśli to ma znaczenie.

wysypka
źródło
2
Byłbym zainteresowany rozwiązaniem, ale podejrzewam, że nie jest to możliwe bez bałagania bezpośrednio w systemie plików.
Kevin
1
Dlaczego potrzebujesz mieć tak duży fizyczny plik? Pytam, bo może uda ci się uniknąć konkatenacji - co, jak pokazują obecne odpowiedzi, jest dość uciążliwe.
liori
6
@ pośpiech: wtedy ta odpowiedź może pomóc: serverfault.com/a/487692/16081
liori
1
Alternatywą dla mapowania urządzeń, mniej wydajną, ale łatwiejszą do wdrożenia i skutkującą urządzeniem, które można podzielić na partycje i można go używać ze zdalnego komputera, jest użycie trybu „wielu” nbd-server.
Stéphane Chazelas
1
Zawsze nazywają mnie głupim, kiedy mówię, że myślę, że to powinno być fajne.
n611x007

Odpowiedzi:

19

AFAIK (niestety) nie jest możliwe obcięcie pliku od samego początku (może to dotyczyć standardowych narzędzi, ale dla poziomu syscall patrz tutaj ). Ale dodając pewną złożoność, możesz użyć normalnego obcięcia (razem z rzadkimi plikami): Możesz pisać na końcu pliku docelowego bez zapisywania wszystkich danych pomiędzy nimi.

Załóżmy, że najpierw oba pliki mają dokładnie 5GiB (5120 MiB) i że chcesz przenieść 100 MiB jednocześnie. Wykonujesz pętlę, która składa się z

  1. kopiowanie jednego bloku z końca pliku źródłowego na koniec pliku docelowego (zwiększenie zajętego miejsca na dysku)
  2. obcięcie pliku źródłowego o jeden blok (zwolnienie miejsca na dysku)

    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
    

Ale spróbuj najpierw z mniejszymi plikami testowymi, proszę ...

Prawdopodobnie pliki nie mają tego samego rozmiaru ani wielokrotności rozmiaru bloku. W takim przypadku obliczenia przesunięć stają się bardziej skomplikowane. seek_bytesi skip_bytesnależy z tego skorzystać.

Jeśli tak chcesz, ale potrzebujesz pomocy w zakresie szczegółów, zapytaj ponownie.

Ostrzeżenie

W zależności od ddrozmiaru bloku wynikowy plik będzie koszmarem fragmentacji.

Hauke ​​Laging
źródło
Wygląda na to, że jest to najbardziej akceptowany sposób łączenia plików. Dzięki za radę.
pędzi
3
jeśli nie ma rzadkiej obsługi plików, możesz odwrócić blokowanie drugiego pliku na miejscu, a następnie po prostu usunąć ostatni blok i dodać go do drugiego pliku
maniak ratchet
1
Sam tego nie próbowałem (chociaż mam zamiar), ale seann.herdejurgen.com/resume/samag.com/html/v09/i08/a9_l1.htm to skrypt Perla, który twierdzi, że implementuje ten algorytm.
zwolnić
16

Zamiast łączyć pliki razem w jeden plik, może symulować pojedynczy plik z nazwanym potokiem, jeśli twój program nie obsługuje wielu plików.

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

Jak sugeruje Hauke, losetup / dmsetup może również działać. Szybki eksperyment; Stworzyłem „plik1..plik4” i przy odrobinie wysiłku:

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

Następnie / dev / dm-0 zawiera wirtualne urządzenie blokowe z plikiem jako zawartością.

Nie przetestowałem tego dobrze.

Kolejna edycja: rozmiar pliku musi być podzielny równomiernie przez 512, inaczej stracisz trochę danych. Jeśli tak, to jesteś dobry. Widzę, że zauważył to również poniżej.

Rob Bos
źródło
To świetny pomysł, aby przeczytać ten plik raz, niestety nie ma możliwości przeskakiwania o pięć do przodu / do tyłu, prawda?
pędzi
7
@rush Lepszą alternatywą może być umieszczenie urządzenia pętlowego na każdym pliku i połączenie ich dmsetupz wirtualnym urządzeniem blokowym (co pozwala na normalne operacje wyszukiwania, ale nie dołącza ani nie obcinają). Jeśli rozmiar pierwszego pliku nie jest wielokrotnością 512, należy skopiować niekompletny ostatni sektor i pierwsze bajty z drugiego pliku (łącznie 512) do trzeciego pliku. Potrzebne byłoby --offsetwówczas urządzenie pętli dla drugiego pliku .
Hauke ​​Laging
eleganckie rozwiązania. +1 również dla Hauke ​​Laging, który sugeruje sposób obejścia problemu, jeśli rozmiar pierwszego pliku (ów) nie jest wielokrotnością 512
Olivier Dulac
9

Musisz napisać coś, co kopiuje dane w pęczkach, które są co najwyżej tak duże, jak ilość wolnego miejsca. Powinno działać tak:

  • Odczytaj blok danych z file2(używając pread(), szukając przed odczytem do właściwej lokalizacji).
  • Dołącz blok do file1.
  • Użyj, fcntl(F_FREESP)aby zwolnić miejsce file2.
  • Powtarzać
Celada
źródło
1
Wiem ... ale nie mogłem wymyślić żadnego sposobu, który nie wymagałby pisania kodu, i pomyślałem, że pisanie tego, co napisałem, było lepsze niż pisanie niczego. Nie myślałem o twojej sprytnej sztuczce, by zacząć od końca!
Celada
Wasze też nie działałoby, nie zaczynając od końca, prawda?
Hauke ​​Laging
Nie, działa od początku, dzięki fcntl(F_FREESP)czemu zwalnia miejsce związane z danym zakresem bajtów pliku (czyni go rzadkim).
Celada
To fajnie. Ale wydaje się być bardzo nową funkcją. Nie wspomniano o tym na mojej fcntlstronie podręcznika (15.04.2012).
Hauke ​​Laging
4
@HaukeLaging F_FREESP to Solaris. W systemie Linux (od wersji 2.6.38) jest to flaga FALLOC_FL_PUNCH_HOLE fallocatesyscall. Nowsze wersje narzędzia fallocate util-linuxmają interfejs do tego.
Stéphane Chazelas
0

Wiem, że jest to bardziej obejście niż to, o co prosiłeś, ale rozwiązałoby problem (z niewielką fragmentacją lub zarysowaniem):

#step 1
mount /path/to/... /the/new/fs #mount a new filesystem (from NFS? or an external usb disk?)

i wtedy

#step 2:
cat file* > /the/new/fs/fullfile

lub jeśli uważasz, że kompresja pomogłaby:

#step 2 (alternate):
cat file* | gzip -c - > /the/new/fs/fullfile.gz

W końcu (i TYLKO wtedy)

#step 3:
rm file*
mv /the/new/fs/fullfile  .   #of fullfile.gz if you compressed it
Olivier Dulac
źródło
Niestety zewnętrzny dysk USB wymaga fizycznego dostępu, a system NFS wymaga dodatkowego sprzętu i nic z tego nie mam. W każdym razie dzięki. =)
pędzi
Pomyślałem, że tak właśnie będzie ... Odpowiedź Rob Bos jest więc najlepszą opcją (bez ryzyka utraty danych przez obcięcie podczas kopiowania i bez naruszenia ograniczeń FS)
Olivier Dulac