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 pvcui mostra le informazioni sullo stato di avanzamento (gli -cNdice "usa una sorta di [c] ursor" e dagli un [N] ame).
Quelle pipe in gzipcui 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 $buffervariabile è 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=fullblockdice dddi leggere fino a $bufferbyte alla connessione attraverso. All'inizio, gzip scriverà un'intestazione, quindi l'output di gzip atterrerà in questa ddriga. Quindi ddattenderà 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 $bufferbyte, questo non sia un problema.
Quindi andiamo in un'altra linea di visualizzazione del tubo e infine sulla nostra ddlinea di uscita . Questa riga ha of(file di output) e conv=notruncspecificato, dove notruncdice di ddnon troncare (eliminare) il file di output prima di scrivere. Quindi se hai 500 byte di Ae scrivi 3 byte di B, il file sarà BBBAAAAA...(invece di essere sostituito da BBB).
Non ho coperto le 2>/dev/nullparti 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 ddscriva troppo quando la linea di buffering passa attraverso, ma con solo 50MiB buffer e 1900MB di /dev/urandomdati, 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).