Comment supprimer un fichier en même temps qu'il est ajouté dans une archive?
Compte tenu du contexte, je vais interpréter cette question comme:
Comment supprimer des données du disque immédiatement après leur lecture, avant la lecture du fichier complet, afin qu'il y ait suffisamment d'espace pour le fichier transformé.
La transformation peut être tout ce que vous voulez faire avec les données: compression, chiffrement, etc.
La réponse est la suivante:
<$file gzip | dd bs=$buffer iflag=fullblock of=$file conv=notrunc
En bref: lisez les données, jetez-les dans gzip (ou tout ce que vous voulez en faire), mettez en mémoire tampon la sortie afin que nous soyons sûrs de lire plus que ce que nous écrivons et de le réécrire dans le fichier. Ceci est une version plus jolie et affiche la sortie lors de l'exécution:
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
Je vais le parcourir ligne par ligne:
cat "$file"
lit le fichier que vous souhaitez compresser. C'est une utilisation inutile de cat (UUOC) car la partie suivante, pv, peut également lire le fichier, mais je trouve cela plus joli.
Il le dirige vers des pv
informations de progression ( -cN
indique qu'il «utilise une sorte de [c] urseur» et lui donne un [N] ame).
Ce canal dans gzip
lequel fait évidemment la compression (lecture depuis stdin, sortie vers stdout).
Ce tuyau dans un autre pv
(vue tuyau).
Cela entre dd bs=$buffer iflag=fullblock
. La $buffer
variable est un nombre, quelque chose comme 50 mégaoctets. C'est cependant beaucoup de RAM que vous souhaitez consacrer à la gestion sûre de votre fichier (en tant que point de données, un tampon de 50 Mo pour un fichier de 2 Go était bien). Le iflag=fullblock
dit dd
de lire jusqu'à $buffer
octets avant de le passer. Au début, gzip écrira un en-tête, donc la sortie de gzip atterrira sur cette dd
ligne. Il dd
attendra ensuite jusqu'à ce qu'il dispose de suffisamment de données avant de les acheminer, afin que l'entrée puisse continuer à lire. De plus, si vous avez des pièces non compressibles, le fichier de sortie peut être plus gros que le fichier d'entrée. Ce tampon s'assure que, jusqu'à $buffer
octets, ce n'est pas un problème.
Ensuite, nous entrons dans une autre ligne de vue de tuyau et enfin sur notre dd
ligne de sortie . Cette ligne a of
(fichier de sortie) et conv=notrunc
spécifiée, où notrunc
indique de dd
ne pas tronquer (supprimer) le fichier de sortie avant d'écrire. Donc, si vous avez 500 octets de A
et que vous écrivez 3 octets de B
, le fichier sera BBBAAAAA...
(au lieu d'être remplacé par BBB
).
Je n'ai pas couvert les 2>/dev/null
pièces et elles sont inutiles. Ils nettoient juste un peu la sortie en supprimant dd
le message "J'ai fini et écrit ce nombre d'octets". Les barres obliques inverses à la fin de chaque ligne ( \
) font que bash traite le tout comme une grosse commande qui se connecte les unes aux autres.
Voici un script complet pour une utilisation plus facile. Pour l'anecdote, je l'ai mis dans un dossier appelé «gz-in-place». J'ai alors réalisé l'acronyme que j'ai fait: GZIP: gnu zip en place. Par la présente, je présente 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!";
J'ai envie d'ajouter une autre ligne de mise en mémoire tampon avant gzip, pour l'empêcher d'écrire trop loin lorsque la dd
ligne de mise en mémoire tampon passe à travers, mais avec seulement 50 Mo de mémoire tampon et 1900 Mo de /dev/urandom
données, cela semble fonctionner de toute façon déjà (les sommes md5 correspondent après la décompression). Rapport assez bon pour moi.
Une autre amélioration serait la détection de l'écriture trop loin, mais je ne vois pas comment faire cela sans enlever la beauté de la chose et créer beaucoup de complexité. À ce stade, vous pourriez tout aussi bien en faire un programme python à part entière qui fait tout correctement (avec des échecs pour éviter la destruction des données).