大容量ファイルの末尾からヌルバイトを削除する

大容量ファイルの末尾からヌルバイトを削除する

次のコマンドを使用して、Linuxディストリビューションを実行しているPCのRaspberry PiからmicroSDカードをバックアップしました。

dd if=/dev/sdx of=file.bin bs=16M

microSDカードは3/4だけ満たされているので、この巨大なファイルの終わりにいくつかのnullバイトを持たせたいと思います。私はそれが必要ではないと確信しています。後でこのコマンドを使用して復元できるように、これらのヌルバイトを最後に効果的に削除するにはどうすればよいですか?

cat file.bin /dev/zero | dd of=/dev/sdx bs=16M

答え1

スペースを節約しながらディスクのバックアップコピーを作成するには、以下を使用しますgzip

gzip </dev/sda >/path/to/sda.gz

バックアップからディスクを復元するには、次を使用します。

gunzip -c /path/to/sda.gz >/dev/sda

これにより、単に末尾のNULバイトを削除するよりも多くのスペースを節約できます。

末尾の NUL バイト除去

末尾のNULバイトを本当に削除し、GNU sedがある場合は、次のことを試すことができます。

sed '$ s/\x00*$//' /dev/sda >/path/to/sda.stripped

大容量ディスクのデータが sed の一部の内部制限を超えると、問題が発生する可能性があります。 GNU sedにはデータサイズのデフォルト制限はありませんが、GNU sed マニュアルシステムメモリの制限により、大容量ファイルを処理できない可能性があることを説明します。

GNU sed には行の長さにデフォルトの制限はありません。より多くの(仮想)メモリをmalloc()できる限り、必要に応じて行を提供または設定できます。

しかし、再帰はサブパターンと無限反復を処理するために使用されます。これは、使用可能なスタックスペースが一部のモードで処理できるバッファサイズを制限できることを意味します。

答え2

この問題を解決するために簡単なツールを書くことができます。

ファイルを読み取り、最後の有効なバイト(nullではない)を見つけて、ファイルを切り捨てます。

ラストの例https://github.com/zqb-all/cut-trailing-bytes:

use std::io;
use std::io::prelude::*;
use std::fs::File;
use std::fs::OpenOptions;
use std::path::PathBuf;
use structopt::StructOpt;
use std::num::ParseIntError;

fn parse_hex(s: &str) -> Result<u8, ParseIntError> {
    u8::from_str_radix(s, 16)
}

#[derive(Debug, StructOpt)]
#[structopt(name = "cut-trailing-bytes", about = "A tool for cut trailing bytes, default cut trailing NULL bytes(0x00 in hex)")]
struct Opt {
    /// File to cut
    #[structopt(parse(from_os_str))]
    file: PathBuf,

    /// For example, pass 'ff' if want to cut 0xff
    #[structopt(short = "c", long = "cut-byte", default_value="0", parse(try_from_str = parse_hex))]
    byte_in_hex: u8,

    /// Check the file but don't real cut it
    #[structopt(short, long = "dry-run")]
    dry_run: bool,
}


fn main() -> io::Result<()> {

    let opt = Opt::from_args();
    let filename = &opt.file;
    let mut f = File::open(filename)?;
    let mut valid_len = 0;
    let mut tmp_len = 0;
    let mut buffer = [0; 4096];

    loop {
        let mut n = f.read(&mut buffer[..])?;
        if n == 0 { break; }
        for byte in buffer.bytes() {
            match byte.unwrap() {
                byte if byte == opt.byte_in_hex => { tmp_len += 1; }
                _ => {
                    valid_len += tmp_len;
                    tmp_len = 0;
                    valid_len += 1;
                }
            }
            n -= 1;
            if n == 0 { break; }
        }
    }
    if !opt.dry_run {
        let f = OpenOptions::new().write(true).open(filename);
        f.unwrap().set_len(valid_len)?;
    }
    println!("cut {} from {} to {}", filename.display(), valid_len + tmp_len, valid_len);

    Ok(())
}

答え3

John1024のコマンドを試してみましたが、sedほとんどの場合はうまくいきましたが、一部の大容量ファイルでは正しくトリムされませんでした。以下は常に有効です。

python -c "open('file-stripped.bin', 'wb').write(open('file.bin', 'rb').read().rstrip(b'\0'))"

まず、ファイルをメモリにロードします。ファイルをチャンクとして扱う適切なPythonスクリプトを作成すると、これを防ぐことができます。

答え4

少なくともLinux(および最新のext4など、これをサポートするファイルシステム)では、これらのゼロfallocate -dシーケンスをディスク領域を占有しない穴に置き換えることができます。

$ echo test > a
$ head -c1G /dev/zero >> a
$ echo test2 >> a
$ head -c1G /dev/zero >> a
$ du -h a
2.1G    a
$ ls -l a
-rw-r--r-- 1 stephane stephane 2147483659 May  5 06:23 a

2GiBの大容量ファイルは2GiBのディスク容量を占めています。

$ fallocate -d a
$ ls -l a
-rw-r--r-- 1 stephane stephane 2147483659 May  5 06:23 a
$ du -h a
12K     a

同じ2GiBファイルですが、ディスクスペースは12KiBしか占有しません。

$ filefrag -v a
Filesystem type is: ef53
File size of a is 2147483659 (524289 blocks of 4096 bytes)
 ext:     logical_offset:        physical_offset: length:   expected: flags:
   0:        0..       0:    7504727..   7504727:      1:
   1:   262144..  262144:   48424960..  48424960:      1:    7766871: last
a: 2 extents found

以下を使用して末尾の穴を削除できます。

truncate -os 262145 a

最後のチャンクにデータを含める必要があります。

$ tail -c4096 a | hd
00000000  00 00 00 00 00 74 65 73  74 32 0a 00 00 00 00 00  |.....test2......|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00001000

最後のブロックから次のゼロを削除することもできますが、ディスク容量が節約されないことに注意してください。

関連情報