背景

之前的文章 二进制文件处理之尾部补0和尾部去0 中介绍了一种使用 sed 去除二进制文件尾部的 NULL(十六进制0x00)字节的方法。

最近发现这种方法有局限性,无法处理较大的文件。因为 sed 本身是行处理,几百M的二进制文件对 sed 而言就是一个几百M的行,这超出了 sed 的最大限制。

具体的限制条件没有去探究,好像有的版本是硬编码了一个上限,有的版本是取决于可申请的内存。

总之,sed 搞不定了,必须另寻他法。

其实我一直相信有现成的工具可以做到,但在有限的时间内没能找到,就只有自己先写一个应应急了。如有人知道简单的办法,可以指教下。

如果只是需要工具,后文就可以略过了,源码在此:https://github.com/zqb-all/cut-trailing-bytes

思路

这个想想倒也简单,就是找到文件最后一个非 0x00 的字节,并把文件从此处截断。

看起来从后往前找效率会高点。但从前往后找简单些,先从最简单的实现开始吧。

实现过程

c 写也很简单,但春节期间看了点 rust 的语法,忍不住想试试。

到 rust 文档中搜了下,https://doc.rust-lang.org/std/io/trait.Read.html#method.bytes 给了一个例子

use std::io;
use std::io::prelude::*;
use std::fs::File; fn main() -> io::Result<()> {
let mut f = File::open("foo.txt")?; for byte in f.bytes() {
println!("{}", byte.unwrap());
}
Ok(())
}

另外找到有个 set_len 直接可以截断文件。

于是第一个版本,照猫画虎很快就出来了。

use std::io;
use std::io::prelude::*;
use std::fs::File;
use std::fs::OpenOptions; fn main() -> io::Result<()> {
let mut f = File::open("foo.txt")?;
let mut total_len = 0;
let mut tmp_len = 0;
for byte in f.bytes() {
match byte.unwrap() {
0 => { tmp_len += 1; }
_ => {
total_len += tmp_len;
tmp_len = 0;
total_len += 1;
}
}
}
println!("total_len:{},tmp_len:{}", total_len, tmp_len);
let mut f = OpenOptions::new().write(true).open("foo.txt");
f.unwrap().set_len(total_len)?;
Ok(())
}

弄了个小文件测试下没问题,那么上大文件试试。

一步到位造个 500M 的文件,结果发现运行之后就卡住,看来这个 f.bytes() ,就跟控制台执行 dd 时指定 bs=1 一样,都是超低效率了。

本来想等等看到底需要多久的,但洗完一个澡回来发现还卡着,就放弃了,直接开始改吧。

改成先读到 buffer 中,再对 buffer 进行逐 byte 判断。

    ...
let mut buffer = [0; 4096];
loop {
let n = f.read(&mut buffer[..])?;
if n == 0 { break; }
for byte in buffer.bytes() {
match byte.unwrap() {
...

效率一下变高了,500M 的文件在我 win10 WSL 中几秒钟就可以跑完。

再改改,把硬编码的 foo.txt 换成从参数中获取文件名,并在 buffer 的处理循环中补上对 n 做递减,递减到 0break,以正确处理最后一笔数据填不满 buffer 的边界情况。

use std::io;
use std::io::prelude::*;
use std::fs::File;
use std::fs::OpenOptions;
use std::env; fn main() -> io::Result<()> {
let args: Vec<String> = env::args().collect();
let filename = &args[1];
let mut f = File::open(filename)?;
let mut total_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() {
0 => { tmp_len += 1; }
_ => {
total_len += tmp_len;
tmp_len = 0;
total_len += 1;
}
}
n -= 1;
if n == 0 { break; }
}
}
println!("len:{}", total_len);
let f = OpenOptions::new().write(true).open(filename);
f.unwrap().set_len(total_len)?;
println!("done"); Ok(())
}

最终版本

上一章的版本其实对我来说暂时够用了,进一步的优化就懒得做了。

周末又完善了一下。改用了 structopt 来处理参数,并支持通过参数指定要裁剪的值。也就是不仅可以用来去除末尾的0x00,也可以指定其他值,例如0xFF

源码不贴了,有需要 github 自取:https://github.com/zqb-all/cut-trailing-bytes

有了参数处理,help 看起来就要舒服多了。

$ cut-trailing-bytes --help
cut-trailing-bytes 0.1.0
A tool for cut trailing bytes, default cut trailing NULL bytes(0x00 in hex) USAGE:
cut-trailing-bytes [FLAGS] [OPTIONS] <file> FLAGS:
-d, --dry-run Check the file but don't real cut it
-h, --help Prints help information
-V, --version Prints version information OPTIONS:
-c, --cut-byte <byte-in-hex> For example, pass 'ff' if want to cut 0xff [default: 0] ARGS:
<file> File to cut

看下效果

$ echo "hello" > hello_00

$ dd if=/dev/zero bs=1k count=1 >> hello_00
1+0 records in
1+0 records out
1024 bytes (1.0 kB, 1.0 KiB) copied, 0.0031857 s, 321 kB/s $ hexdump -C hello_00
00000000 68 65 6c 6c 6f 0a 00 00 00 00 00 00 00 00 00 00 |hello...........|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000406 $ du -b hello_00
1030 hello_00 $ ./cut-trailing-bytes hello_00
cut hello_00 from 1030 to 6 $ hexdump -C hello_00
00000000 68 65 6c 6c 6f 0a |hello.|
00000006 $ du -b hello_00
6 hello_00 $ echo "hello" > hello_ff $ dd if=/dev/zero bs=1k count=1 | tr '\000' '\377' >> hello_ff
1+0 records in
1+0 records out
1024 bytes (1.0 kB, 1.0 KiB) copied, 0.0070723 s, 145 kB/s $ hexdump -C hello_ff
00000000 68 65 6c 6c 6f 0a ff ff ff ff ff ff ff ff ff ff |hello...........|
00000010 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
*
00000406 $ du -b hello_ff
1030 hello_ff $ ./cut-trailing-bytes hello_ff -c ff
cut hello_ff from 1030 to 6 $ hexdump -C hello_ff
00000000 68 65 6c 6c 6f 0a |hello.|
00000006 zqb@WSL:~/workspace/rust/cut-trailing-bytes
$ du -b hello_ff
6 hello_ff

题外话

rust 编译出来的可执行文件还是挺大的,后来发现改用 nightly 版本会小很多, 再做一次 strip 就更小了。

最终是上面贴出源码的版本,strip 后的 release 版本是 200+ k, 而 github 上完善了参数处理等的版本则要 700+ k

公众号:https://sourl.cn/qVmBKh

Blog: https://www.cnblogs.com/zqb-all/p/12641329.html

cut-trailing-bytes:二进制尾部去0小工具的更多相关文章

  1. iKcamp出品|微信小程序|工具安装+目录说明|基于最新版1.0开发者工具初中级教程分享

    iKcamp官网:http://www.ikcamp.com 访问官网更快阅读全部免费分享课程:<iKcamp出品|全网最新|微信小程序|基于最新版1.0开发者工具之初中级培训教程分享>. ...

  2. C语言中的位操作(16)--计算二进制数字尾部连续0的数目

    本篇文章介绍计算二进制数字尾部连续0的数目的相关算法,例如:v=(1101000)2,该数尾部连续0的数目=3 方法1:线性时间算法 unsigned int v; // 需要计算的目标整数 int ...

  3. js实现去文本换行符小工具

    js实现去文本换行符小工具 一.总结 一句话总结: 1.vertical属性使用的时候注意看清定义,也注意父元素的基准线问题.vertical-align:top; 2.获取textareaEleme ...

  4. Django2.0+小程序技术打造微信小程序助手✍✍✍

    Django2.0+小程序技术打造微信小程序助手  整个课程都看完了,这个课程的分享可以往下看,下面有链接,之前做java开发也做了一些年头,也分享下自己看这个视频的感受,单论单个知识点课程本身没问题 ...

  5. Linux小工具的应用,grep,sort,wc,cut

    小工具的使用: 1.管道(|):连接多个命令的工具,进程之间通讯的一种方式 用法:命令1 | 命令2 | 命令3....2.grep工具:行过滤,打印出的结果一行一行的 用法:grep options ...

  6. 微信小程序教学第二章:小程序中级实战教程之预备篇 - 项目结构设计 |基于最新版1.0开发者工具

    iKcamp官网:http://www.ikcamp.com 访问官网更快阅读全部免费分享课程:<iKcamp出品|全网最新|微信小程序|基于最新版1.0开发者工具之初中级培训教程分享>. ...

  7. 生鲜配送管理系统_升鲜宝V2.0 小标签打印功能说明_15382353715

    小标签打印说明 小标签打印可以打印本系统的订单商品数量,也可以把外部的订单商品导入本系统进行打印. 打印本系统中的订单商品操作说明 1.1    界面说明 1.2     查询条件 1.2.1     ...

  8. SpringBoot2.0小程序支付功能实现weixin-java-pay

    SpringBoot2.0小程序支付功能实现weixin-java-pay WxJava - 微信开发 Java SDK(开发工具包); 支持包括微信支付.开放平台.公众号.企业微信/企业号.小程序等 ...

  9. N阶乘尾部的0个数

    N阶乘尾部的0个数 描述 设计一个算法,计算出n阶乘中尾部零的个数 思路: 1.1 * 2 * 3 * ... * n --> 1 * 2 * 3 * (2 * 2) * 5 * (2 * 3) ...

随机推荐

  1. d3学习day3 --y轴添加文本标签

    y轴添加文本标签 g.append("g") .call(y_axis) .append("text") .text("price($)") ...

  2. 解决IOS下window.open页面打不开问题

    问题如标题所写,在ajax回调里面拿到即将要跳转的链接url,使用window.open(linkUrl),没有起作用,而且代码也没有报错,查找原因是:大部分现代的浏览器(Chome/Firefox/ ...

  3. 数据结构 4 时间复杂度、B-树 B+树 具体应用与理解

    前言 面试中,经常会问到有关于MYSQL 索引的相关概念,我们之前也都学过有关树的概念.以及二叉树.二叉查找树.红黑树等.这一节,来关注经常是数据库索引中使用的B-树 在说这些之前,我们需要了解时间复 ...

  4. MySQL的万字总结(缓存,索引,Explain,事务,redo日志等)

    hello,小伙伴们,好久不见,MySQL系列停更了差不多两个月了,也有小伙伴问我为啥不更了呢?其实我去看了MySQL的全集,准备憋个大招,更新篇长文(我不会告诉你是因为我懒的). 好了,话不多说,直 ...

  5. ALSA 声卡 驱动 linux 4.1.36 中变化

    linux 4.1.36 中变化 1ret = request_irq(IRQ_DMA2, s3c2440_dma2_irq, IRQF_DISABLED, "myalsa for play ...

  6. 面试总被问分布式ID怎么办? 滴滴(Tinyid)甩给他

    整理了一些Java方面的架构.面试资料(微服务.集群.分布式.中间件等),有需要的小伙伴可以关注公众号[程序员内点事],无套路自行领取 一口气说出 9种 分布式ID生成方式,面试官有点懵了 面试总被问 ...

  7. Oracle 11g rac中关于crsctl stop cluster/crs/has的区别

    转载至http://www.oracleplus.net/arch/1203.html,整理后得. 1 通过命令查看cluster/has/crs管理的内容 [root@11rac1 ~]# crsc ...

  8. 前后端分离基于Oauth2的SSO单点登录怎样做?

    一.说明 单点登录顾名思义就是在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统,免除多次登录的烦恼:本文主要介绍跨域间的 前后端分离 项目怎样实现单点登录,并且与 非前后端分离 的差 ...

  9. html5 拖放购物车

    1.本例中模仿了购物车添加的功能 主要运用了ondragstart / ondragover/ ondrag 功能 功能比较简单 遗留问题:火狐下图片拖进会被打开 <!doctype html& ...

  10. JavaScript 基础知识汇总目录

    一.标签.代码结构.现代模式.变量.数据类型.类型转换 GO 二.运算符.值的比较.交互.条件运算符.逻辑运算符 GO 三.循环 while 和 for .switch语句.函数.函数表达式和箭头函数 ...