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 pv
das Fortschrittsinformationen -cN
angezeigt werden ( sagt, dass es eine Art [c] ursor verwendet und ihm eine [N] ame gibt).
Die Pipes, in gzip
die 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 $buffer
Variable 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=fullblock
sagt dd
, dass bis zu $buffer
Bytes gelesen werden sollen, bevor es durchgeleitet wird. Zu Beginn schreibt gzip einen Header, sodass die Ausgabe von gzip in dieser dd
Zeile landet . Dann dd
warten , 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 $buffer
Bytes 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=notrunc
angegeben, wobei angegeben wird, notrunc
dass dd
die Ausgabedatei vor dem Schreiben nicht abgeschnitten (gelöscht) werden soll. Wenn Sie also 500 Bytes haben A
und Sie schreiben 3 Byte B
, wird die Datei sein BBBAAAAA...
(anstatt werden ersetzt durch BBB
).
Ich habe die 2>/dev/null
Teile nicht abgedeckt und sie sind unnötig. Sie räumen die Ausgabe nur ein wenig auf, indem sie die dd
Meldung "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 dd
beim Durchlaufen der Pufferzeile zu weit schreibt , aber mit nur 50 MB Puffer und 1900 MB /dev/urandom
Daten 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).