从大文件末尾删除空字节

从大文件末尾删除空字节

我刚刚使用以下命令从运行 Linux 发行版的 PC 上的 Raspberry Pi 备份了 microSD 卡:

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

microSD 卡只有 3/4 满,所以我想在这个巨大文件的末尾有一些空字节。我非常确定我不需要那个。如何有效地从末尾去除这些空字节,以便稍后可以使用此命令恢复它?

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

您可以编写一个简单的工具来解决这个问题。

读取文件,找出最后一个有效字节(不为空),然后截断文件。

一个 Rust 的例子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

虽然您也可以删除最后一个块中的尾随零,但请注意,它不会节省磁盘上的任何空间。

相关内容