Hängen Sie große Dateien aneinander an, ohne sie zu kopieren

41

Es gibt 5 riesige Dateien (Datei1, Datei2, .. Datei5) mit jeweils ca. 10 GB und extrem wenig freiem Speicherplatz auf der Festplatte, und ich muss alle diese Dateien zu einer zusammenfassen. Es ist nicht erforderlich, die Originaldateien zu behalten, sondern nur die endgültigen.

Übliche Verkettung mit wird catfür Dateien in Folge file2.. file5:

cat file2 >> file1 ; rm file2

Leider erfordert dieser Weg mindestens 10 GB freien Speicherplatz, den ich nicht habe. Gibt es eine Möglichkeit, Dateien zu verketten, ohne sie tatsächlich zu kopieren, aber dem Dateisystem irgendwie mitzuteilen, dass Datei1 nicht am ursprünglichen Ende von Datei1 endet und beim Start von Datei2 fortgesetzt wird?

ps. Dateisystem ist ext4, wenn das wichtig ist.

eilen
quelle
2
Es würde mich interessieren, eine Lösung zu finden, aber ich vermute, dass dies nicht möglich ist, ohne direkt mit dem Dateisystem zu experimentieren.
Kevin
1
Warum müssen Sie eine einzige physische Datei haben, die so groß ist? Ich frage, weil Sie das Verketten vielleicht vermeiden können - was, wie die aktuellen Antworten zeigen, ziemlich lästig ist.
Liori
6
@ Ansturm: Dann könnte diese Antwort helfen: serverfault.com/a/487692/16081
Liori
1
Alternative zu Device-Mapper, weniger effizient, aber einfacher zu implementieren und führt zu einem partitionierbaren Gerät und kann von einem Remote-Computer verwendet werden, ist die Verwendung des "Multi" -Modus von nbd-server.
Stéphane Chazelas
1
Sie nennen mich immer dumm, wenn ich sage, dass ich denke, dass das cool sein sollte.
n611x007

Antworten:

19

AFAIK es ist (leider) nicht möglich, eine Datei von Anfang an abzuschneiden (dies kann für die Standardtools zutreffen, aber für die Syscall-Ebene siehe hier ). Mit etwas mehr Komplexität können Sie jedoch die normale Kürzung (zusammen mit Dateien mit geringer Dichte) verwenden: Sie können bis zum Ende der Zieldatei schreiben, ohne alle Daten dazwischen geschrieben zu haben.

Angenommen, beide Dateien haben genau 5 GB (5120 MB) und Sie möchten jeweils 100 MB verschieben. Sie führen eine Schleife aus

  1. Kopieren eines Blocks vom Ende der Quelldatei zum Ende der Zieldatei (Erhöhung des belegten Speicherplatzes)
  2. Kürzen der Quelldatei um einen Block (Freigeben von Speicherplatz)

    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
    

Aber probieren Sie es zuerst mit kleineren Testdateien aus, bitte ...

Wahrscheinlich haben die Dateien weder die gleiche Größe noch ein Vielfaches der Blockgröße. In diesem Fall wird die Berechnung der Offsets komplizierter. seek_bytesund skip_bytessollte dann verwendet werden.

Wenn dies der Weg ist, den Sie gehen möchten, aber Hilfe für die Details benötigen, dann fragen Sie erneut.

Warnung

Abhängig von der ddBlockgröße ist die resultierende Datei ein Fragmentierungs-Albtraum.

Hauke ​​Laging
quelle
Sieht so aus, als wäre dies der akzeptabelste Weg, Dateien zu verketten. Danke für den Rat.
Ansturm
3
Wenn es keine Unterstützung für spärliche Dateien gibt, können Sie die zweite Datei blockweise umkehren und dann einfach den letzten Block entfernen und zur zweiten Datei hinzufügen
Ratschenfreak
1
Ich habe es selbst nicht ausprobiert (obwohl ich im Begriff bin), aber seann.herdejurgen.com/resume/samag.com/html/v09/i08/a9_l1.htm ist ein Perl-Skript, das behauptet, diesen Algorithmus zu implementieren.
26.
16

Anstatt die Dateien zu einer Datei zusammenzufassen, können Sie auch eine einzelne Datei mit einer Named Pipe simulieren, wenn Ihr Programm nicht mehrere Dateien verarbeiten kann.

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

Wie Hauke ​​vorschlägt, kann auch losetup / dmsetup funktionieren. Ein schnelles Experiment; Ich habe 'file1..file4' erstellt und mit ein wenig Mühe folgendes getan:

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

Dann enthält / dev / dm-0 ein virtuelles Blockgerät mit Ihrer Datei als Inhalt.

Ich habe das nicht gut getestet.

Eine weitere Änderung: Die Dateigröße muss gleichmäßig durch 512 teilbar sein, da sonst Daten verloren gehen. Wenn ja, dann bist du gut. Ich sehe, dass er das auch unten notiert hat.

Rob Bos
quelle
Es ist eine großartige Idee, diese Datei einmal zu lesen. Leider ist es nicht möglich, über Fifos vorwärts / rückwärts zu springen, nicht wahr?
Ansturm
7
@rush Die überlegene Alternative könnte darin bestehen, jeder Datei ein Loop-Gerät hinzuzufügen und sie dmsetupzu einem virtuellen Block-Gerät zu kombinieren (das normale Suchvorgänge ermöglicht, aber weder anfügt noch abschneidet). Wenn die Größe der ersten Datei nicht ein Vielfaches von 512 ist, sollten Sie den unvollständigen letzten Sektor und die ersten Bytes aus der zweiten Datei (in Summe 512) in eine dritte Datei kopieren. Das Loop-Gerät für die zweite Datei würde --offsetdann brauchen .
Hauke ​​Laging
elegante lösungen. +1 auch an Hauke ​​Laging, der eine Möglichkeit vorschlägt, das Problem zu umgehen, wenn die Größe der ersten Datei (en) nicht ein Vielfaches von 512 ist
Olivier Dulac
9

Sie müssen etwas schreiben, das Daten in Gruppen kopiert, die höchstens so groß sind wie der verfügbare Speicherplatz. Es sollte so funktionieren:

  • Lesen Sie einen Datenblock aus file2(indem Sie pread()vor dem Lesen nach dem richtigen Speicherort suchen).
  • Hänge den Block an file1.
  • Verwenden Sie fcntl(F_FREESP)diese Option, um die Zuordnung des Speicherplatzes von aufzuheben file2.
  • Wiederholen
Celada
quelle
1
Ich weiß ... aber ich konnte mir keinen Weg vorstellen, bei dem es nicht darum ging, Code zu schreiben, und ich dachte, das zu schreiben, was ich schrieb, war besser als nichts zu schreiben. Ich habe nicht an deinen cleveren Trick gedacht, am Ende anzufangen!
Celada
Auch Ihre würde nicht funktionieren, ohne am Ende anzufangen, oder?
Hauke ​​Laging
Nein, es funktioniert von Anfang an, fcntl(F_FREESP)wodurch der mit einem bestimmten Byte-Bereich der Datei verknüpfte Speicherplatz freigegeben wird (wodurch die Datei sparsam wird).
Celada
Das ist ziemlich toll. Scheint aber ein sehr neues Feature zu sein. Es wird in meiner fcntlManpage (15.04.2012) nicht erwähnt .
Hauke ​​Laging
4
@HaukeLaging F_FREESP ist Solaris. Unter Linux (seit 2.6.38) ist es das Flag FALLOC_FL_PUNCH_HOLE des Syscalls fallocate. Neuere Versionen des Dienstprogramms fallocate from util-linuxhaben eine Schnittstelle dazu.
Stéphane Chazelas
0

Ich weiß, es ist eher eine Problemumgehung als das, wonach Sie gefragt haben, aber es würde Ihr Problem lösen (und mit wenig Fragmentierung oder Kratzern):

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

und dann

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

oder, wenn Sie glauben, dass die Komprimierung helfen würde:

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

Dann (und NUR dann) endlich

#step 3:
rm file*
mv /the/new/fs/fullfile  .   #of fullfile.gz if you compressed it
Olivier Dulac
quelle
Leider erfordert externe USB-Festplatte physischen Zugriff und NFS erfordert zusätzliche Hardware und ich habe nichts davon. Trotzdem danke. =)
Rush
Ich dachte, es wäre so ... Rob Bos 'Antwort ist dann, was Ihre beste Option zu sein scheint (ohne zu riskieren, Daten durch Abschneiden während des Kopierens zu verlieren und ohne FS-Einschränkungen zu treffen)
Olivier Dulac