Google
 

几个文本处理的小题目(续一):使用awk

在飞机上用《sed与awk》消磨时间时,想起网友以前提过的一个问题:

1 按指定列的长度排序

1.1 问题

这个网友有以下格式的词库(in.txt):

w=我 
bm=标 
ceq=陈 
wm=我们 
nnyl=努 
wm,=我们 
djh=大家好 
tdmd=他们 
tzm=同志们 
tzm,=同志们 
djhnv=大家好 
ppaa=平平安安 
tzmdv=同志们 
ppaa,=平平安安 
wrmfw=为人民服务 
gzrzf=工作认真负责 

他希望对词库按照这样的规则排序:将=看作列分隔符,先按照第二列的长度排序,再按第一列的长度排序,再按第二列编码排序,再按第一列编码排序。例如,上面一段示例的排序结果是:

w=我
bm=标
ceq=陈
nnyl=努
wm=我们
wm,=我们
tdmd=他们
djh=大家好
tzm=同志们
tzm,=同志们
djhnv=大家好
tzmdv=同志们
ppaa=平平安安
ppaa,=平平安安
wrmfw=为人民服务
gzrzf=工作认真负责

我说用excel可以实现。网友说excel太慢了,而且不能支持几十万行的词组。让我们能不能用awk解决这个问题。

1.2 awk简介

awk是一个神奇的文本处理工具。它按照我们的命令逐行处理输入的文本。我们把命令写在一个脚本文件中,例如在一个叫作test.awk的文件中写一行命令:

{print $2}

然后念动咒语,让awk执行我们的命令:

awk -F= -f test.awk in.txt

我们就会得到以下输出:

我
标
陈
我们
努
我们
大家好
他们
同志们
同志们
大家好
平平安安
同志们
平平安安
为人民服务
工作认真负责

awk的特点是它把文本看作数据列表,每行数据包括由分隔符分隔的多个数据项。默认的分隔符是空格或制表符。我们可以在命令行上用-F 参数指定分隔符,例如“-F=”命令awk用“=”作为分隔符。 -f 参数指定要执行的脚本文件。

再看看命令。命令被放在{}中,在{}前面可以指定命令的作用范围,即对哪些行执行这个命令。如果没有指定作用范围,就对所有行执行命令。 awk用$1表示每行的第一个数据项,用$2表示第二个数据项,依此类推。$0可以用来引用整行文本。"print $2"就是打印第二项,所以我们就看到了上面的结果。

让我们下达一个新命令,将test.awk修改为:

{print length($2) "\t" $2}

再念动咒语:

awk -F= -f test.awk in.txt

我们这次得到了:

1       我
1       标
1       陈
2       我们
1       努
2       我们
3       大家好
2       他们
3       同志们
3       同志们
3       大家好
4       平平安安
3       同志们
4       平平安安
5       为人民服务
6       工作认真负责

length函数可以求字符串的长度,length($2)就是第二列字符串的长度。"\t"是制表符。我们检查一下第一列的数字是不是中文词组的长度?

awk还能支持类似于C语言的printf函数,这样我们能精确地控制输出。例如将test.awk修改为:

{printf("%02d%02d\t%s\t%s\n", length($2), length($1), $2, $1)}

如果读者熟悉printf函数,应该很容易看懂这行命令。这次我们得到的输出是:

0101    我      w
0102    标      bm
0103    陈      ceq
0202    我们    wm
0104    努      nnyl
0203    我们    wm,
0303    大家好  djh
0204    他们    tdmd
0303    同志们  tzm
0304    同志们  tzm,
0305    大家好  djhnv
0404    平平安安        ppaa
0305    同志们  tzmdv
0405    平平安安        ppaa,
0505    为人民服务      wrmfw
0605    工作认真负责    gzrzf

我们在原来的文本前面加了一列,这列的每一项是4个数字,前两个数字是中文词组的长度,后面两个数字是编码的长度。

1.3 神奇时刻

我们再准备一个命令文件output.awk,内容是:

{print $3"="$2}

读者能看懂这个命令的含义吗?然后我们念动一长串咒语:

awk -F= -f test.awk in.txt|sort|awk -f output.awk>output.txt

我们得到了输出文件output.txt,它的内容是:

w=我
bm=标
ceq=陈
nnyl=努
wm=我们
wm,=我们
tdmd=他们
djh=大家好
tzm=同志们
tzm,=同志们
djhnv=大家好
tzmdv=同志们
ppaa=平平安安
ppaa,=平平安安
wrmfw=为人民服务
gzrzf=工作认真负责

这就是我们需要的结果。“awk -F= -f test.awk in.txt”的意思前面已经说过了。它的输出通过管道“|”被传给sort。 sort对输入排序后又通过管道传给“awk -f output.awk”。命令“print $3"="$2”打印了输入的第三列和第二列并用“=”分隔这两列。 最后,我们将“awk -f output.awk”的输出重定向到output.txt,就得到了上面的结果。

1.4 在windows上执行

我把本文用到的工具和例子放到了我的主页上(下载)。读者解压后,在命令行进入sortit目录,执行:

awk -F= -f test.awk in.txt|lsort|awk -f output.awk>output.txt

就可以得到上面描述的结果。在windows上为了不与windows的sort冲突,我把sort程序更名为lsort。

2 结束语

awk是一个文本处理的中级魔法,学习过这种魔法的人就可以驾驭它。有一本介绍这种魔法的书叫做《sed与awk》。 我读了前面两章,然后通过查找书末的快速参考和几次尝试,解决了一个文本处理的小问题。

这本魔法书,除了awk外,还介绍一个叫做sed的中级魔法。awk和sed都是处理文本流的工具,文本流过它们,按照我们的命令变成我们想要的形状。 如果说awk是专门处理文本列表的编程语言,sed就是把很多编辑命令预先写好在一个叫作脚本的文件里,然后对一个或很多文件执行这个脚本。 sed擅长大量的查找和替换。综合使用这两个魔法,再加上sort、grep等初级魔法的帮助,我们可以轻松完成很多文本处理工作。

 

Google
 

个人主页留言本我的空间我的程序 fmdd@263.net