Come eliminare un file contemporaneamente che viene aggiunto in un archivio?
Dato il contesto, interpreterò questa domanda come:
Come rimuovere i dati dal disco immediatamente dopo la lettura, prima che sia stato letto l'intero file, in modo che vi sia spazio sufficiente per il file trasformato.
La trasformazione può essere qualsiasi cosa tu voglia fare con i dati: compressione, crittografia, ecc.
La risposta è questa:
<$file gzip | dd bs=$buffer iflag=fullblock of=$file conv=notrunc
In breve: leggi i dati, gettali in gzip (o qualunque cosa tu voglia fare con esso), buffer l'output in modo che siamo sicuri di leggere più di quanto scriviamo e riscriverli nel file. Questa è una versione più carina e mostra l'output durante l'esecuzione:
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
Lo esaminerò, riga per riga:
cat "$file"
legge il file che si desidera comprimere. È un uso inutile di cat (UUOC) poiché la parte successiva, pv, può anche leggere il file, ma trovo che questo sia più carino.
Lo convoglia in pv
cui mostra le informazioni sullo stato di avanzamento (gli -cN
dice "usa una sorta di [c] ursor" e dagli un [N] ame).
Quelle pipe in gzip
cui ovviamente esegue la compressione (lettura da stdin, output a stdout).
Che convoglia in un altro pv
(vista del tubo).
Che convoglia dd bs=$buffer iflag=fullblock
. La $buffer
variabile è un numero, qualcosa come 50 megabyte. È comunque molta RAM che vuoi dedicare alla gestione sicura del tuo file (come punto dati, il buffer da 50 MB per un file da 2 GB andava bene). La iflag=fullblock
dice dd
di leggere fino a $buffer
byte alla connessione attraverso. All'inizio, gzip scriverà un'intestazione, quindi l'output di gzip atterrerà in questa dd
riga. Quindi dd
attenderà fino a quando non dispone di dati sufficienti prima di inviarlo e quindi l'input può leggere ulteriormente. Inoltre, se si hanno parti non comprimibili, il file di output potrebbe essere più grande del file di input. Questo buffer si assicura che, fino a $buffer
byte, questo non sia un problema.
Quindi andiamo in un'altra linea di visualizzazione del tubo e infine sulla nostra dd
linea di uscita . Questa riga ha of
(file di output) e conv=notrunc
specificato, dove notrunc
dice di dd
non troncare (eliminare) il file di output prima di scrivere. Quindi se hai 500 byte di A
e scrivi 3 byte di B
, il file sarà BBBAAAAA...
(invece di essere sostituito da BBB
).
Non ho coperto le 2>/dev/null
parti e non sono necessarie. Hanno semplicemente riordinato un po dd
' l'output sopprimendo il messaggio "Ho finito e ho scritto così tanti byte". Le barre rovesciate alla fine di ogni riga ( \
) fanno sì che bash tratti l'intera cosa come un grande comando che si convoglia l'uno nell'altro.
Ecco uno script completo per un uso più semplice. Aneddoticamente, l'ho messo in una cartella chiamata 'gz-in-place'. Ho quindi realizzato l'acronimo che ho creato: GZIP: gnu zip sul posto. Quindi con la presente presento 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!";
Ho voglia di aggiungere un'altra linea di buffering prima di gzip, per evitare che dd
scriva troppo quando la linea di buffering passa attraverso, ma con solo 50MiB buffer e 1900MB di /dev/urandom
dati, sembra funzionare già comunque (gli md5sums si sono abbinati dopo la decompressione). Rapporto abbastanza buono per me.
Un altro miglioramento sarebbe il rilevamento della scrittura troppo lontano, ma non vedo come farlo senza rimuovere la bellezza della cosa e creare molta complessità. A quel punto, potresti anche renderlo un vero programma Python che fa tutto correttamente (con errori per prevenire la distruzione dei dati).