Chinaunix首页 | 论坛 | 博客
  • 博客访问: 280541
  • 博文数量: 47
  • 博客积分: 1455
  • 博客等级: 上尉
  • 技术积分: 385
  • 用 户 组: 普通用户
  • 注册时间: 2008-04-26 08:59
文章分类

全部博文(47)

文章存档

2012年(1)

2011年(1)

2009年(23)

2008年(22)

我的朋友

分类: LINUX

2008-04-27 14:23:37

VIM中二进制文件编辑
 
编辑二进制不是VIM的强项, 但也并非不可能, 长期与打印机语言打交道逼着我要经常处理各种不同格式的二进制文件. 想象一下用VIM就可以让一个可打印的二进制文件某个色面消失, 变换, 甚至, 将它转换回一个图象文件.

老的方案: 最早出于DEBUG的需要, 曾经用C语言写过打印机文件(以下简称PRT文件)的Parser, 就是要从PRT文件中把数据部分提取出来, 观察RIP生成的色彩数据. 同时验证命令格式是否正确. 很显然, 对于开发者来说, 这些PRT的格式是经常变动的, 而且我要处理的不是一个而是很多种不同格式的PRT文件, C语言的开发周期源码->编译->运行->调试不十分适合这个目标, 而且这些程序很可能是写了一次就扔掉.
接下来我用flex来解析二进制文件, 虽然这样的用法也不是lex/flex设计者的初衷, 但解析二进制数据是可以的, flex中能力适中的regexp可以表达我的所需. 这减轻了我一部分负担. 不用在C语言中对文件指针上下腾挪跌打翻滚了. 使用一段时间之后我发现这仍然不是一个理想的方案, 最初我是在flex文件里做几乎所有的工作, 把RLE数据的解压, 数据转换和偏移处理都放在了这里, 但后来发现这样的调试成本比较高, 而我开发的环境又是windows+cygwin下用的flex, 编译用gcc, 因为没有找到其它的windows下flex工具可用. 所以我调试的唯一手段就是printf, 用大量的assert提醒自己非预期条件的发生. assert帮了不少忙, 但往往还需要在出错的时侯知道出错时的周边信息, 比如文件处理到什么位置时出了错误, 所以最经常的做法就是assert失败之后还要回到出错的源代码位置, 再加一个条件判断, 如果失败就用printf把对DEBUG有用的信息显示出来, 往往是ftell(fp).
对flex方案的改进是我只用flex 来解析数据, 不做数据处理了, 解析的结果只是一个文本文件, 其格式如下:
[C] dataoff=102, len=58
意思是对于Cyan, 发现了一行数据, 该行数据在PRT文件中的偏移是102, 数据长度是58. 全部二进制文件Parse完成后的结果是
[C] dataoff=102, len=58
[M] dataoff=187, len=17
[Y] dataoff=192, len=0
[K] dataoff=205, len=18

如此循环. 工作被划分为不同的子任务, 而且由程序的不同部分或不同的程序来完成, 这样简化了很多工作, 而且使得每一个阶段的输出结果都容易验证. 接下来的工作是根据这个文件对数据的描述, 生成CMYK不同色面的数据. 最后的工作是把这4个色面的数据合并成一个TIF文件.

应该说到这一步基本上是一个成本上可以接受的方案了. 解析一个新格式的PRT文件可能只需要写十几二十行的flex 规则. 剩余的处理模块可以通用.

但我还是想更进一步, 用我所精熟的VIM来直接从编辑器里转换图象文件, VIM有接口可以调用外部程序, 也有一个编辑器内置的脚本语言. 不过缺点是这个语言很简单. 不能作为通用的计算机语言来使用(象EMACS中的elisp那样), 比如作用函数参数传递的内容不能包括的数据. 光这一点我就没法接受, 因为我要处理的正是这样特征的数据, 每一个BYTE数据都可能是从0到255的任何值. 翻文档, 找到其中一段讲处理的:
===================================================
characters in the file are stored as in memory. In the display
they are shown as "^@". The translation is done when reading and writing
files. To match a with a search pattern you can just enter CTRL-@ or
"CTRL-V 000". This is probably just what you expect. Internally the
character is replaced with a in the search pattern. What is unusual is
that typing CTRL-V CTRL-J also inserts a , thus also searches for a
in the file. {Vi cannot handle characters in the file at all}

*CR-used-for-NL*
When 'fileformat' is "mac", characters in the file are stored as
characters internally. In the display they are shown as "^M". Otherwise this
works similar to the usage of for a .

When working with expression evaluation, a character in the pattern
matches a in the string. The use of "\n" (backslash n) to match a
doesn't work there, it only works to match text in the buffer.

*pattern-multi-byte*
Patterns will also work with multi-byte characters, mostly as you would
expect. But invalid bytes may cause trouble, a pattern with an invalid byte
will probably never match.
===================================================
而实际使用上
let @a="a0b"
echo strlen(@a)
输出结果是1

如果写一个函数:
function! Tmp( dat )
return a:dat
endfunction
然后用这样的方法来调用这样的函数
%s#bin_regexp#\=Tmp(submatch(0))#g
按道理来说调用之后缓冲区的内容应该什么都不改变才对, 但实际上不是这样, 函数返回a:dat 就会把第一个之后的字符丢弃了.

在函数里做手脚的想法又失败了, 我原来的企图是找到这样一个二进制数据, 然后通过 submatch函数把它传递给自定义的函数, 在自定义的函数里处理这个二进制数据, 但既然函数参数不能用来处理, 我也就只好作罢了. 因为VIM在内部实际把转换成传递给程序, 但我又没有办法区分一个作为换行的真实的和一个作为内部表示的

但是, 也正因为VIM在内部把表示为所以在函数中对submatch(0) 求字符串长度是正确的. 这样我倒是可以仅仅捕捉这个pattern在文件中的位置和长度即可. 象上面的flex方案一样, 让另一个程序根据对数据偏移和长度的描述去取数据.
继续想办法...

VIM作为一个优秀的文本编辑器, 它的很多细微方面的行为仍然是未良好定义的, 比如手册中没有指出下面的替换发生时:
%s#regexp#\=UserDefinedFunction()#g
对于每一个找到的regexp, UserDefinedFunction中可以做的事情是有限制的, 一些与当前缓冲区, 光标位置相关的函数应如何表现, 比如line('.'), col('.') 函数是否返回被找到的那个光标位置. 从我的实验结果来看, 的确如此, 但是, 同时我也发现了一些其它的问题.
比如有些命令行为是整个系统内只此一份的, 外面执行的redir如果被某个函数中另一个redir所覆盖, 那可实在是无从查起. 没有地方报告冲突, 错误, 或警告. 而在最新的vim7中, 如果设置了set binary encoding=latin1时, line2byte(1)的返回值竟然是-1, 对同一个文件作同样的设置用vim6.3版本操作返回正确的1.

abaabaaab
%s#a\b#&#g
上面这个标的当然只会发生3次, 对于缓冲区的影响来说. 内容不变. 但
%s#a\ze\b#&#g
的替换次数就不同了, \ze具有限定一个regexp返回范围的作用, 对于aab来说, 整个regexp确实是匹配3个字符aab, 但返回的位置却是第一个a, 而下一次的搜索则从第二个a开始, 所以整个表达式实际上匹配了6次.

所以说\zs和\ze并不象我自己根据使用总结出来的那样, 仅仅是PERL中(?=)和(?

阅读(1775) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~