/root/bigfile
100GBのシステムに80GBのファイルがあり、このファイルをアーカイブに入れたいとしましょう /root/bigarchive.tar
このファイルがアーカイブに追加されると同時に削除する必要があるのは明らかです。したがって、私の質問:
ファイルがアーカイブに追加されると同時に削除する方法は?
/root/bigfile
100GBのシステムに80GBのファイルがあり、このファイルをアーカイブに入れたいとしましょう /root/bigarchive.tar
このファイルがアーカイブに追加されると同時に削除する必要があるのは明らかです。したがって、私の質問:
ファイルがアーカイブに追加されると同時に削除する方法は?
回答:
単一ファイルの非圧縮tarアーカイブは、ヘッダー、ファイル、および末尾パッドで構成されます。したがって、主な問題は、ファイルの先頭に512バイトのヘッダーを追加する方法です。まず、ヘッダーのみで目的の結果を作成します。
tar cf - bigfile | dd count=1 >bigarchive.tar
次に、ファイルの最初の10Gをコピーします。簡単のために、あなたのddが一度に1Gibを読み書きできると仮定します:
dd count=10 bs=1G if=bigfile >>bigarchive.tar
ここで、元のファイルからコピーしたデータの割り当てを解除します。
fallocate --punch-hole -o 0 -l 10GiB bigfile
これにより、データがファイルシステム上のスペースを取らないスパースゼロに置き換えられます。この方法で続行し、a skip=10
を次のに追加してからdd
、fallocate
開始オフセットをに増やします-o 10GiB
。最後にいくつかのNUL文字を追加して、最終的なtarファイルを埋め込みます。
ファイルシステムがサポートしていない場合、fallocate
同様のことを行うことができますが、ファイルの最後から開始します。まず、ファイルの最後の10 GBを、という名前の中間ファイルにコピーしますpart8
。次に、truncate
コマンドを使用して元のファイルのサイズを縮小します。それぞれ10ギバイトのファイルが8つになるまで、同様に進めます。その後、ヘッダーとpart1
toを連結しbigarchive.tar
、次にを削除part1
してから、連結part2
して削除します。
ファイルを削除しても、あなたが思っていることを実行するとは限りません。これが、UNIXライクなシステムではシステムコールが呼び出されunlink
、呼び出されない理由delete
です。マニュアルページから:
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.
結果として、データコンプレッサー/アーカイバーがファイルから読み取っている限り、そのファイルは存在し続け、ファイルシステムのスペースを占有します。
ファイルがアーカイブに追加されると同時に削除する方法は?
コンテキストを考慮して、この質問を次のように解釈します。
完全なファイルが読み取られる前に、読み取られた直後にディスクからデータを削除して、変換されたファイルに十分なスペースを確保する方法。
変換は、圧縮、暗号化など、データに対して実行したいあらゆることを行うことができます。
答えはこれです:
<$file gzip | dd bs=$buffer iflag=fullblock of=$file conv=notrunc
つまり、データを読み取り、それをgzip(またはそれを使用して何をしたいか)に入れ、出力をバッファリングして、書き込みよりも確実に読み取り、ファイルに書き戻します。これはよりきれいで、実行中に出力を表示するバージョンです。
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
行ごとに説明します。
cat "$file"
圧縮するファイルを読み取ります。次の部分のpvもファイルを読み取ることができるので、猫(UUOC)を使用するのは無意味ですが、私はこれがきれいだと思います。
それをパイプしてpv
、進捗情報を表示します(-cN
「ある種の[c]カーソルを使用する」と伝え、[名前]を付けます)。
パイプgzip
は明らかに圧縮を行います(標準入力から読み取り、標準出力に出力します)。
それは別のpv
パイプ(パイプビュー)にパイプします。
それはにパイプしdd bs=$buffer iflag=fullblock
ます。$buffer
変数は数、50メガバイトのようなものです。ただし、ファイルを安全に処理するために必要なRAMの量は非常に多くなります(データポイントとして、2 GBのファイルには50 MBのバッファで十分でした)。iflag=fullblock
告げるdd
まで読むために$buffer
それを通すことの前にバイト。最初に、gzipはヘッダーを書き込むため、gzipの出力はこのdd
行に配置されます。次に、dd
十分なデータが得られるまで待機してからパイプを介し、入力がさらに読み取れるようにします。さらに、圧縮できない部分がある場合、出力ファイルは入力ファイルよりも大きくなる可能性があります。このバッファは、最大$buffer
バイトまで、これが問題ではないことを確認します。
次に、別のパイプビューラインに進み、最後に出力dd
ラインに進みます。この行にはof
(出力ファイル)があり、conv=notrunc
指定されnotrunc
てdd
います。書き込み前に出力ファイルを切り捨てない(削除しない)ように指示します。あなたは、500バイトを持っているのであればA
、あなたは3つのバイトを書き込みB
、ファイルは次のようになりますBBBAAAAA...
(代わりにすること置き換えることでBBB
)。
2>/dev/null
パーツはカバーしなかったので不要です。彼らは、dd
「私はこれで終わり、これだけのバイト数を書き込んだ」というメッセージを抑制して、出力を少し片付けます。各行の終わりにあるバックスラッシュ(\
)は、bashが全体を1つの大きなコマンドとして相互にパイプ処理するようにします。
簡単に使用できる完全なスクリプトを次に示します。事例として、「gz-in-place」というフォルダに入れました。次に、私が作った頭字語GZIP:gnu zip in-placeに気づきました。ここに私が提示する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!";
私はgzipの前に別のバッファリング行を追加したいのですが、バッファリングdd
行がフラッシュするときに過度に書き込まれるのを防ぐためですが、50MiBバッファと1900MBの/dev/urandom
データしかなければ、とにかくすでに機能しているようです(解凍後にmd5sumsが一致します)。私には十分な比率です。
もう1つの改善点は、書きすぎを検出しすぎることですが、美しさを取り除き、複雑さを大幅に増やさずにそれを行う方法はわかりません。その時点で、すべてを適切に実行する本格的なpythonプログラムにすることもできます(データの破壊を防ぐためのフェイルセーフを使用)。