awk实现GB2312向Big5的内码转换
Awk读入数据是以记录为单位,每次读入一条记录的数据(记录默认是以“行”为单位,但可以通过设置“RS”来设置记录分隔符),加上支持数字运算、数组和格式输出,很适合用做“格式数据处理”的工具。此外Awk之所以强大正是因为它支持数字运算,这使得它的用途甚至超出了一般的文本处理的范畴。比如我们接下来示范的用awk来实现的GB2312向Big5码的内码转换。……
by hq00e
awk有多个版本,这里所用的awk版本是GNU awk。
awk实现GB2312向Big5的内码转换
什么是awk?awk是一种对符合选定的条件(式样)的内容进行操作的编程语言。我们可以通过它对文本内容进行非交互式的编辑。它的语法与C语言相似,但增加了许多文本编辑的特性——尤其最重要的是正则表达式。不过他们还有一大区别就是awk是解释语言,而且解释程序本身相当小,一般在400K以内。只要打开记事本,你就可以编程了。如果你已经会C语言了,那你只需要花一点时间来学正则表达式就可以使用awk了。如果你不大会编程,那我向你保证,awk比C语言简单很多。正则表达式是awk的强力武器,不过我们下面的内容并不涉及正则表达式,也不要求读者会awk——就算不会awk,内容本身也是相当好理解的。
1 准备合适的内码转换表
由于GB2312和Big5码的标准是各自制定的,因而没法通过算法来进行它们之间的内码转换。我们只能通过转换表来实现。应该注意的是通过一对一的转换得到的结果并非完全准确的,因为在简体和繁体中的字符是一对多甚至是多对多对应的。当然我们暂且不考虑那些“深层次”的问题,现在我们将目光集中在如何实现通过转换表进行内码转换。
网上可以找到多个版本的gb-big5表,示例脚本里的用的表是由A1到FF段的gb-big5表,可以到这里下载。
如果手头已经有内码转换的软件的话也可以做转换表。可以很容易在gawk来生成gb2312的字符表。
unix平台: gawk 'BEGIN{for (a=0xa1;a<0xff;a++) for(b=0xa0;b<=0xff;b++) printf "%c%c",a,b }" >gb.TXT
win平台: gawk "BEGIN{for (a=0xa1;a<0xff;a++) for(b=0xa0;b<=0xff;b++) printf \"%c%c\",a,b }" >gb.TXT
用现有的软件对gb2312表进行转换(有些软件会对结果进行修改导致表不准确),换成Big5的,就获得了一张gb2312-big5的转换表。根据GB2312字符的机器码,进行换算就得到对其在GB2312-Big5表中的位置(但此时该字符为Big5的字符所替代)。将该位置的字符就是该GB2312字符对应的Big5字符。以同样的方式我们可以得到Big5-GB2312的转换表。
现在我们有转换表了,下一步就是在gawk中实现国标码和Big5码之间的相互转换。假设我们要实现gb-big5的转换,那要怎么开始呢?
2 在gawk中读入转换表
首先应该读入转换表,我们可以将转换表中的字依序存放在数组中。我们只要将输入字符的机器码进行换算再作为数组的下标就可以得到相对应的big5字符了。其实也可以不先读入转换表,而是每次转换时打开文件根据机器码换算在文件中的偏移量。但在awk中读打开关闭文件的操作相当耗时——所以我们还是采用预先读入表到数组中。
二是字符在表中的位置是与机器码相对应的,因此我们要得到输入字符的机器码(即字符的十六进制值)。awk没有提供内建类似ord()的函数。但我们可以手工添加(注:在gawk的文档中也有一段类似的内容。在gawk文档查找一下“`chr'”“ord”看有什么收获!)。添加的方法,我们在后面在探讨。现在先看一下读入转换表的函数怎么写:
1 func getTable( i,k){
2 if ((getline < "gb2big5.tab")>0 )
3 for (i=1;i<=NF;i++) {
4 tab[++k]=$i $(i+1)
5 i++
6 } #每次读入两个字节
7 }
第1行中我们自己定义了一个函数:getTable ,有三个参数。我们会在awk脚本的`BEGIN'段中,通过以下方式调用这个函数以初始化转换数组。
BEGIN{ getTable(tab) }
实际上我们在调用这个函数时只用了一个参数“tab”,但在定义函数时我们却定义了三个参数。这是因为awk在参数列表中指定本地(局部)变量,通过在参数表中指定变量,我们将i和k声明为本地变量。
第2行的代码从“gb2big5.tab”中读取一行数据——实际上这个文件也只有一行(很长的一行)。
第3-5行的代码将读入的数据(转换表),以每两个字节(相当于一个汉字)为单位存放在相应序号的数组里。这样转换表中的前两个字节(也就是第一个Big5码字),将放在tab[1]中。第三、四个字节(也就是转换表中的第二个Big5码字),将放在tab[2]中。
3 在gawk中获取字符的机器码
下一步,我们办法获得输入字符的机器码。比如,在GB2312中“阿”的机器码是十六进制的“b0 a2”。在当读入“阿”时,我们要有一个函数ord("阿")的时候能返回“b0 a2”。不过因为国码码是两字节的编码,我们可以分两次处理使用ord()分别来得到“阿”高位和低位的机器码。GB2312中汉字是高低字节都是从A0开始到FF结束的,所以我们反其道而行——先通过机器码杖举对应的字符,将结果用数组存起来。这样我们就可以建立一个机器码与字符相对应的表了。这样我们就可以通过字符来查找机器码了。在awk中可以用下面的代码实现:
func _ord_init( i ) {
for (i=0xA0;i<=0xFF;i++) ord[sprintf("%c",i)]=i
}
有没有注意到awk的代码相当紧凑呢?sprintf是awk内建的函数,用c语言的人应该相当熟悉了。sprintf将数值转换为相应的字符(%c),并返回该字符。ord则将该字符作为数组的下标,所存的值正是用做下标的符的机器码。我们只要以输入字符作为数组的索引就能得到相应的机器码了。
提示:awk将下标都视作字符,因而对awk来说a[1]和a["1"]是一样的。事实上awk中数字和字符的介定相当模糊,数字字串经常可以不经显示转换地当成数值使用。看一下下面的例子:
gawk 'BEGIN{str="123"; print str+3}' 输出:126
gawk 'BEGIN{str="a123"; print str+3}' 输出:3
注意第二个字串中含有非数字字符,被当成0对待。另外在awk中下标可以是任意长度的任意字符,这给我们很大的灵活性——你可能会发现这与perl中的hash表的概念很相近。
4 用机器码计算字符在表中的位置
现在我们已经读入了转换表,也知道怎样获取字符对应的机器码了。接下我们要知道怎样将gb2312编码汉字的机器码换算为转换表中相应的字符的位置。换算公式很简单,我们以“h_b”表示高位的机器码,“l_b”表示低位的机器码:
(h_b-161)*96+l_b-160+1
“阿”高位是0xb0,低位是0xa2。分别是十进制的176和162,代入上面的公式:
(176-161)*96+162-160+1=1443
而数组ord[1443]的值就是我们想要的那个Big5的“阿”了。
5 完整的转换脚本
现在,我们只要将上面的内容结合起来就很容易用awk写出一个将gb码转换为big5码的程序了:
############ 开始 ##########
#!/usr/bin/gawk -f
# filename: gb2big5.awk
# gb2312码转big5码程序
BEGIN{ FS=OFS="" ; getTable(); _ord_init()}
func getTable( i,k){
if ((getline < "gb2big5.tab")>0 )
for (i=1;i<=NF;i++) {tab[++k]=$i $(i+1); i++}
}
func _ord_init( i ) {
for (i=0x40;i<=0xFF;i++) ord[sprintf("%c",i)]=i
}
{
for(i=1;i<=NF;i++) {
h_b=ord[$i]
if (h_b >= 161) {
l_b=ord[$(i+1)]
$i=tab[(h_b-161)*96+l_b-160+1]
$(i+1)=""
i++
}
}
print
}
############ 结束 ##########
连空行,我们只用了24行就写了一个内码转换(GB2312 -> Big5)的脚本。这个脚本相当容易理解,如果还有需要解释的地方的话就是FS和OFS了。在BEGIN段中的:
FS=OFS=""
通过将栏位分隔符(Field Separator即FS)设为空字串,使用awk将每个字节视为一个Field。这样我们就可以通过for语句逐个字节地读入数据,下面的语句将当前Record中的数据逐字节地存在数组arr中:
for (i=1;i<=NF;i++) { arr[i]=$i }
6 运行结果
现在我们看一下运行结果:
hq00e@somewhere ~ $ cat aa.txt 简体中文测试abcd hq00e@somewhere ~ $ gawk -f gb2big5.awk aa.txt 簡體中文測試abcd
以同样的方式我们还可以很容易地写出将Big5转为GB2312码的脚本。大家不防自已动手试试。
![hq00e[a]126.com](http://static.flickr.com/56/120355805_7079a475f9_m.jpg)



看不懂,不过还是收藏了。还有请教一下你的格式是怎么做出来的?
Comment by Anonymous — 2006, April 4 @ 22:17
以texinfo格式写文档,再用makeinfo生成html。
Comment by hq00e — 2006, April 27 @ 18:05