80GBのファイルがあるとしましょう/root/bigfile
100GBシステムで、このファイルをアーカイブに入れたい/root/bigarchive.tar
このファイルがアーカイブに追加されると同時に削除する必要があるのは明らかです。したがって、私の質問:
アーカイブに追加されると同時にファイルを削除する方法
GNU tar
コマンドを使用している場合は、--remove-files
オプション:
--remove-files
アーカイブに追加した後にファイルを削除する
tar -cvf files.tar --remove-files my_directory
単一ファイルの非圧縮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
これにより、データがsparseゼロに置き換えられ、ファイルシステム上でスペースを取りません。この方法で続けて、skip=10
を次のdd
に追加し、fallocate
開始オフセットを-o 10GiB
に増やします。最後に、いくつかのNUL文字を追加して、最終的なtarファイルを埋め込みます。
ファイルシステムがfallocate
をサポートしていない場合、同様のことを行うことができますが、ファイルの最後から開始します。まず、ファイルの最後の10Gバイトをpart8
などの中間ファイルにコピーします。次に、truncate
コマンドを使用して、元のファイルのサイズを減らします。それぞれ10ギバイトのファイルが8つになるまで、同様に進めます。次に、ヘッダーとpart1
を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]カーソルを使用して」、[N] ameを指定します)。
そのパイプはgzip
にパイプします。これは明らかに圧縮を行います(stdinから読み取り、stdoutに出力します)。
それは別のpv
(パイプビュー)にパイプします。
それはdd bs=$buffer iflag=fullblock
にパイプします。 $buffer
変数は、50メガバイトなどの数値です。ただし、ファイルの安全な処理に専念したいのはどれだけRAMです(データポイントとして、2GBファイル用の50MBバッファは問題ありませんでした)。 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!";
別のバッファリング行beforegzipを追加したいのですが、バッファリングdd
行がフラッシュするときに過度に書き込まれないようにしますが、50MiBバッファと1900MBの/dev/urandom
だけですデータ、それはとにかく既に動作しているようです(解凍後にmd5sumsが一致しました)。私には十分な比率です。
もう1つの改善点は、書き過ぎを検出することですが、美しさを取り除いて複雑さを大幅に増やさずにそれを行う方法はわかりません。その時点で、すべてを適切に実行する本格的なpythonプログラムにすることもできます(データの破壊を防ぐためのフェイルセーフを使用)。