web-dev-qa-db-ja.com

巨大なファイルをアーカイブに追加し、同時に削除する方法

80GBのファイルがあるとしましょう/root/bigfile 100GBシステムで、このファイルをアーカイブに入れたい/root/bigarchive.tar

このファイルがアーカイブに追加されると同時に削除する必要があるのは明らかです。したがって、私の質問:

アーカイブに追加されると同時にファイルを削除する方法

8
user123456

GNU tarコマンドを使用している場合は、--remove-filesオプション:

--remove-files

アーカイブに追加した後にファイルを削除する

tar -cvf files.tar --remove-files my_directory
0
Dababi

単一ファイルの非圧縮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つになるまで、同様に進めます。次に、ヘッダーとpart1bigarchive.tarに連結し、part1を削除してから、part2を連結して削除します。

6
meuh

ファイルを削除しても、あなたが思っていることを実行するとは限りません。 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.

結果として、データコンプレッサー/アーカイバーがファイルから読み取っている限り、そのファイルは存在し続け、ファイルシステムのスペースを占有します。

5
AlexP

ファイルがアーカイブに追加されると同時に削除する方法は?

コンテキストを考慮して、この質問を次のように解釈します。

ファイルが完全に読み取られる前に、読み取られた直後にディスクからデータを削除して、変換されたファイルに十分なスペースを確保する方法

変換は、圧縮、暗号化など、データに対して実行したいあらゆることを行うことができます。

答えはこれです:

<$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プログラムにすることもできます(データの破壊を防ぐためのフェイルセーフを使用)。

1
Luc