So fügen Sie einem Archiv eine große Datei hinzu und löschen sie parallel

8

Angenommen, ich habe eine Datei mit 80 GB /root/bigfileauf einem 100-GB-System und möchte diese Datei in einem Archiv ablegen /root/bigarchive.tar

Ich muss diese Datei natürlich gleichzeitig mit dem Hinzufügen im Archiv löschen. Daher meine Frage:

Wie lösche ich eine Datei gleichzeitig mit dem Hinzufügen in einem Archiv?

user123456
quelle

Antworten:

0

Wenn Sie den tarBefehl GNU verwenden , können Sie die folgende --remove-filesOption verwenden:

--remove-files

Entfernen Sie Dateien, nachdem Sie sie dem Archiv hinzugefügt haben

tar -cvf files.tar --remove-files my_directory
Dababi
quelle
5
Ich denke, das OP möchte die Datei zur gleichen Zeit entfernen, zu der sie archiviert wird. Wenn also --remove-files nach dem Hinzufügen der Datei zur .tar-Datei entfernt wird, ist dies für ihn nicht hilfreich, da seine Festplatte leer ist Platz.
Zumo de Vidrio
6

Ein unkomprimiertes Teerarchiv einer einzelnen Datei besteht aus einem Header, der Datei und einem Trailing Pad. Ihr Hauptproblem besteht also darin, dem Anfang Ihrer Datei 512 Byte Header hinzuzufügen. Sie können beginnen, indem Sie das gewünschte Ergebnis nur mit der Kopfzeile erstellen:

tar cf - bigfile | dd count=1 >bigarchive.tar

Kopieren Sie dann die ersten 10G Ihrer Datei. Der Einfachheit halber gehen wir davon aus, dass Ihr dd jeweils 1Gib lesen / schreiben kann:

dd count=10 bs=1G if=bigfile >>bigarchive.tar

Wir geben jetzt die kopierten Daten aus der Originaldatei frei:

fallocate --punch-hole -o 0 -l 10GiB bigfile

Dies ersetzt die Daten durch spärliche Nullen, die keinen Platz im Dateisystem beanspruchen. Fahren Sie auf diese Weise fort, indem Sie a skip=10zum nächsten hinzufügen ddund dann den fallocateStartversatz auf erhöhen -o 10GiB. Fügen Sie ganz am Ende einige Nullzeichen hinzu, um die endgültige TAR-Datei auszufüllen.


Wenn Ihr Dateisystem dies nicht unterstützt fallocate, können Sie etwas Ähnliches tun, jedoch am Ende der Datei beginnen. Kopieren Sie zuerst die letzten 10 Gibytes der Datei in eine Zwischendatei mit dem Namen z part8. Verwenden Sie dann den truncateBefehl, um die Größe der Originaldatei zu verringern. Gehen Sie ähnlich vor, bis Sie 8 Dateien mit jeweils 10Gibyte haben. Sie können dann den Header verketten und part1an bigarchive.tar, dann entfernen part1und dann verketten part2und entfernen und so weiter.

meuh
quelle
5

Das Löschen einer Datei macht nicht unbedingt das, was Sie denken. Deshalb wird in UNIX-ähnlichen Systemen der Systemaufruf aufgerufen unlinkund nicht delete. Von der Handbuchseite:

unlink() deletes a name from the filesystem.  If that name was the last
link to a file and no processes have the file open, the file is deleted
and the space it was using is made available for reuse.

If the name was the last link to a file but any processes still have
the file open, the file will remain in existence until  the  last  file
descriptor referring to it is closed.

Infolgedessen bleibt diese Datei vorhanden, solange der Datenkompressor / Archivierer aus der Datei liest, und belegt Speicherplatz im Dateisystem.

AlexP
quelle
1

Wie lösche ich eine Datei gleichzeitig mit dem Hinzufügen in einem Archiv?

In Anbetracht des Kontextes werde ich diese Frage wie folgt interpretieren:

So entfernen Sie Daten unmittelbar nach dem Lesen von der Festplatte, bevor die vollständige Datei gelesen wurde, damit genügend Speicherplatz für die transformierte Datei vorhanden ist.

Die Umwandlung kann alles sein, was Sie mit den Daten tun möchten: Komprimieren, Verschlüsseln usw.

Die Antwort lautet:

<$file gzip | dd bs=$buffer iflag=fullblock of=$file conv=notrunc

Kurz gesagt: Lesen Sie Daten, werfen Sie sie in gzip (oder was auch immer Sie damit machen möchten), puffern Sie die Ausgabe, damit wir sicher mehr lesen als schreiben, und schreiben Sie sie zurück in die Datei. Dies ist eine schönere Version, die die Ausgabe während des Betriebs zeigt:

cat "$file" \
| pv -cN 'bytes read from file' \
| gzip \
| pv -cN 'bytes received from compressor' \
| dd bs=$buffer iflag=fullblock 2>/dev/null \
| pv -cN 'bytes written back to file' \
| dd of="$file" conv=notrunc 2>/dev/null

Ich werde es Zeile für Zeile durchgehen:

cat "$file"liest die Datei, die Sie komprimieren möchten. Es ist eine nutzlose Verwendung von cat (UUOC), da der nächste Teil, pv, die Datei ebenfalls lesen kann, aber ich finde das hübscher.

Es leitet es weiter, in pvdas Fortschrittsinformationen -cNangezeigt werden ( sagt, dass es eine Art [c] ursor verwendet und ihm eine [N] ame gibt).

Die Pipes, in gzipdie offensichtlich die Komprimierung erfolgt (Lesen von stdin, Ausgeben an stdout).

Das leitet in ein anderes pv(Rohransicht).

Das pfeift hinein dd bs=$buffer iflag=fullblock. Die $bufferVariable ist eine Zahl, ungefähr 50 Megabyte. Es ist jedoch viel RAM, das Sie für die sichere Handhabung Ihrer Datei verwenden möchten (als Datenpunkt waren 50 MB Puffer für eine 2-GB-Datei in Ordnung). Das iflag=fullblocksagt dd, dass bis zu $bufferBytes gelesen werden sollen, bevor es durchgeleitet wird. Zu Beginn schreibt gzip einen Header, sodass die Ausgabe von gzip in dieser ddZeile landet . Dann ddwarten , bis es genügend Daten hat , bevor es durch Rohrleitungen, so dass die Eingabe weiter lesen kann. Wenn Sie nicht komprimierbare Teile haben, ist die Ausgabedatei möglicherweise größer als die Eingabedatei. Dieser Puffer stellt sicher, dass dies bis zu $bufferBytes kein Problem ist.

Dann gehen wir in eine andere Pipe-View-Linie und schließlich auf unsere Ausgangslinie dd. Diese Zeile hat of(Ausgabedatei) und conv=notruncangegeben, wobei angegeben wird, notruncdass dddie Ausgabedatei vor dem Schreiben nicht abgeschnitten (gelöscht) werden soll. Wenn Sie also 500 Bytes haben Aund Sie schreiben 3 Byte B, wird die Datei sein BBBAAAAA...(anstatt werden ersetzt durch BBB).

Ich habe die 2>/dev/nullTeile nicht abgedeckt und sie sind unnötig. Sie räumen die Ausgabe nur ein wenig auf, indem sie die ddMeldung "Ich bin fertig und habe so viele Bytes geschrieben" unterdrücken . Die Backslashes am Ende jeder Zeile ( \) lassen bash das Ganze als einen großen Befehl behandeln, der ineinander geleitet wird.


Hier ist ein vollständiges Skript zur einfacheren Verwendung. Anekdotisch habe ich es in einen Ordner namens "gz-in-place" gelegt. Dann erkannte ich das Akronym, das ich gemacht hatte: GZIP: gnu zip in-place. Hiermit präsentiere ich GZIP.sh:

#!/usr/bin/env bash

### Settings

# Buffer is how many bytes to buffer before writing back to the original file.
# It is meant to prevent the gzip header from overwriting data, and in case
# there are parts that are uncompressible where the compressor might exceed
# the original filesize. In these cases, the buffer will help prevent damage.
buffer=$((1024*1024*50)) # 50 MiB

# You will need something that can work in stream mode from stdin to stdout.
compressor="gzip"

# For gzip, you might want to pass -9 for better compression. The default is
# (typically?) 6.
compressorargs=""

### End of settings

# FYI I'm aware of the UUOC but it's prettier this way

if [ $# -ne 1 ] || [ "x$1" == "x-h" ] || [ "x$1" == "x--help" ]; then
    cat << EOF
Usage: $0 filename
Where 'filename' is the file to compress in-place.

NO GUARANTEES ARE GIVEN THAT THIS WILL WORK!
Only operate on data that you have backups of.
(But you always back up important data anyway, right?)

See the source for more settings, such as buffer size (more is safer) and
compression level.

The only non-standard dependency is pv, though you could take it out
with no adverse effects, other than having no info about progress.
EOF
    exit 1;
fi;

b=$(($buffer/1024/1024));
echo "Progressing '$1' with ${b}MiB buffer...";
echo "Note: I have no means of detecting this, but if you see the 'bytes read from";
echo "file' exceed 'bytes written back to file', your file is now garbage.";
echo "";

cat "$1" \
| pv -cN 'bytes read from file' \
| $compressor $compressorargs \
| pv -cN 'bytes received from compressor' \
| dd bs=$buffer iflag=fullblock 2>/dev/null \
| pv -cN 'bytes written back to file' \
| dd of="$1" conv=notrunc 2>/dev/null

echo "Done!";

Ich möchte vor gzip eine weitere Pufferzeile hinzufügen , um zu verhindern, dass sie ddbeim Durchlaufen der Pufferzeile zu weit schreibt , aber mit nur 50 MB Puffer und 1900 MB /dev/urandomDaten scheint sie ohnehin schon zu funktionieren (die MD5-Summen stimmen nach dem Dekomprimieren überein). Gut genug Verhältnis für mich.

Eine weitere Verbesserung wäre die Erkennung von zu weitem Schreiben, aber ich sehe nicht, wie ich das tun kann, ohne die Schönheit der Sache zu entfernen und viel Komplexität zu schaffen. An diesem Punkt können Sie es genauso gut zu einem vollwertigen Python-Programm machen, das alles richtig macht (mit Failafes, um Datenvernichtung zu verhindern).

Luc
quelle