AWK做字典查找程序
AWK做字典查找程序
输出满足“特定条件”(通常是匹配式样)的内容是awk最基本的功能。上一篇中我们将这个“特定条件”(由随机数指定的记录数)放在awk脚本中,让awk每次随机输出记录。这一篇中我们将会演示用传递变量的方式在每次运行脚本时由用户指定这一“特定条件”。
by hq00e
上一篇中我们已经完成了一个用gawk实现的用来随机读取表情符号的shell脚本readsmiley。这一篇中我们将示范如何用gawk显示符合特定条件的内容。同时会继续完善上一个脚本,使之可接收参数。像这样:
$./smiley '8:]'
8:] From a gorilla
Gorilla
而在不使用参数时,该脚本仍像上一篇中那样随机地显示表情符号。
不过在那之前我们会先讲解一个简单的例子——gawk的查字典脚本。
背景知识
正则表达式与字串。在awk中正则表达式是`/regexp/‘的形式出现的——正则表达式的两端是`/‘。而字串则是以`"string"‘的形式出现——两端是`"‘。但字串也可以用作正则表达式,所不同的是在`"‘号中使用正则表达式需要使用转义符。
在awk中比较字串可以使用==与~,两者的不同表现在两个地方:
- ~将等式右边的值视为正则表达式,等式左边只能使用字串。==将等式两边都视为字串。
- 使用前一个逻辑运算符时,两边只有完成一致才返回真值。使用~时,只要运算符左边的字串含有左边字串就为真。
以字串表正则时转义符的使用。在字串形式的正则表达式的规则与一般的正则表达式稍有不同,如:与正则表达式`/\/file\.ext/‘等价的字串形式的正则表达式是`"\\/file\\.ext"‘。考虑下面的例子:
hq00e@somewhere ~
$ gawk 'BEGIN{if("/file.ext" ~ "\\/file\\.ext")print "true"}'
true
hq00e@somewhere ~
$ gawk 'BEGIN{if("/file.ext" ~ "/file.ext")print "true"}'
hq00e@somewhere ~
$ gawk 'BEGIN{if("/file.ext" == "/file.ext")print "true"}'
true
第一、二个例子中,使用了`~‘运算符,等式右边的内容被视为是正则表达式。而最后一个例子中使用了`==‘,两边的内容都是字串。至于在字串中使用正则表达式的具体内容可参阅相关的文档,不再赘述。
gawk接收外部变量的方法是使用命令行参数-v。比如要在脚本外部定义一个变量`myword‘,并在脚本中引用可用下面的命令:
$ gawk -v myword="hello" 'BEGIN{print myword}'
`-v‘与`myword‘中的空格也可以省略。将用的技巧是通过`-v‘选项在脚本外部定义`RS‘和`FS‘1:
$ gawk -vRS="" ...
上面的命令在脚本外部定义了`RS‘为连续的空行。在命令行中使用时这样可以减少一些输入——在脚本中输入则为`BEGIN{RS=""}‘。在Windows的命令中使用时这样可以减少转义字符`\‘的使用。
写一个查字典脚本
现在开始构造一个查字典的脚本。这个脚本完成的所功能相当简单,我们可以很容易地用其他Unix工具完成同样的任务。但这个脚本所用到的技巧,将为以后写更复杂的gawk脚本奠定基础。
找到合适的字典文件
首先要找到合适的字典。我手头上的字典是以前在汉化新世经网站找到的。像下面的这种的格式:
…… hellfire 地狱之火 hellgramite 翅虫的幼虫 hellgrammite 翅虫的幼虫 hellhole 不舒服场所 hellhound 地狱之犬 hellion 坏人 hellish 地狱般的 hellkite 残忍的人 hello 喂 helluva 很难的 hellward 向地狱 helm 舵 ……
前面是英文单词后面是对应的中文解释。中英文之间用制表符隔开。每行对应一个单词或短语。
如果你找到的字典是其他格式的话,我们可以通过sed、gawk,等命令来转换,或者修改gawk脚本中的`RS‘。我手头上有一本英西字典的格式如下:
August [蓴藧g蓹st] agosto Australian [蓴streili蓹n] australiano Austrian [蓴stri蓹n] austriaco
可以看到记录与记录之间是用空行隔开的而不是我们想要的以制表符隔开。但是可以使用gawk脚本,转换为我们想要的格式:
hq00e@somewhere ~
$ gawk 'BEGIN{RS=""; FS="\n"}{print $1 "\t" $2}' eng-spa.dict
...
August [蓴藧g蓹st] agosto
Australian [蓴streili蓹n] australiano
Austrian [蓴stri蓹n] austriaco
...
但是更好的办法应该是将`RS‘设为`""‘!
字典脚本
现在我们要查找单词`tunami‘。使用如下命令:gawk '/tunami/{print $0}' dict.txt现在我们知道这条命令也可以写为gawk '/tunami/' dict.txt。但我们需要让它能接收外部变量这样我们就能在命令行使用。gawk -vtarget="$1" '$0~target' dict.txt。我们输入的文字是被当作正则表达式使用的,但这样使用相当麻烦就算我们想真想用正则表达式,看一下这个例子:
gawk -vtarget="B\\.C" '$0~target' dict.txt
(上面的脚本只要给的参数是中文就能查找对应的英文了)
我们在`.‘前用了两个反斜杠使gawk将其当成一个普通的句号。不想使用正则表达式的话将`$0~target‘改为`$1==target‘。
现在我们有一个能使用正则表式的的字典查找脚本了。慢,这不是用grep就可以做到了吗?`grep tunami dict.txt‘。……的确是的……,汗。不过将上面的脚本进行扩充,我们还可以做一个交互式的词典!下面是脚本的内容:
#!/usr/bin/gawk -f
# ----------------------------------------开始
# 从CL读单词,然后从"dict.txt"找单词
# 在每次进行查找时,我们使用getline来重新读入字典。这种方式速度
# 较慢,但对内存要求没那么高。另一种实现的方式是通过关联数组。以
# 关联数组实现的话速度快,但使用的内存比较多(具体的量与字典的大
# 小有关)。
# 使用方式:`gawk -f lookup.awk' #不需要参数
BEGIN{FS="\t" ; RS="\n"
printf "输入要查找的单词(输入“qquit”退出):"
}
{
# 读取用户输入
word_to_find=tolower($1)
# 退出条件
if(word_to_find~/^qquit$/)exit
# 输入空格或没输入的话,不进行进一步处理
if(word_to_find!~/^[ \t]*$/){
# 去除一小部分字元避免不便。
# 保留常用字元如:^.*+$
gsub(/[-[&]/,"\\\\&",word_to_find)
# 读入字典文件
while((getline<"dict.txt")>0){
# 此处的$1与先前的$1不同。前一为命
# 令的$1,现在这个$1是"dict.txt"的$1。
# 不区分大小写。
if (tolower($1)~word_to_find){
print $0
}
}
# 关闭字典文件
close("dict.txt")
}
printf "\n输入要查找的单词(输入“qquit”退出):"
}
# ----------------------------------------结束
这个字典引擎支持部分正则表达式字元,不区分大小写。下面是使用示例:
$ ./lookup.awk 输入要查找的单词(输入“qquit”退出):tunami tunami 海啸 输入要查找的单词(输入“qquit”退出):^CrE..t$ credit 贷方 credit 贷记;信贷 credit 信用;贷款;贷方;债权 输入要查找的单词(输入“qquit”退出):qquit
打造全功能的`smiley‘命令
我们接下来看一下如何为我们上一次写的readsmiley脚本增加功能使之能接收命令行参数。你应该已经知道了它的实现原理与上面的字典脚本将会是一样的。我们将通过gawk的-v选项向gawk传送变量参数。废话不多说先看脚本:
#!/usr/bin/sh
gawk -v sface="$1" 'BEGIN{
RS=""
FS="\t"
}
# 不需要用到正则表达式,所以下
# 面的比较用了“==”运算符。
$1==sface' mysmiley
将上面的脚本命名为readgivensmiley,修改执行权限,就可以执行了。这个脚本要求一个参数。下面是运行的结果:
$ ./readgivensmiley ":-*"
:-* Kiss...
Oooops (covering mouth with hand)
User just ate a sour pickle
User just ate something sour
Smiley after eating something bitter
After eating something bitter or sour
Star-tled
Blowing a kiss
需要注意的是,shell脚本第二行中的`$1‘与最后一行中的`$1‘是不一样的。前者表示的是bash shell的第一个参数——即上面运行示例中的`:-*‘。这个参数在传给变量`sface‘之前会被展开为`:-*‘(注意:`$1‘两边用了双引号`"‘,确保shell能将它当作一个命令行参数变量并展开为变量所表示的值。)而后者是在gawk的脚本之中,表示的是记录中的第一栏(即表情符号的部分)。另外我们比较字串用的运算符是`==‘这要求用户给的参数要与表情完全一样才能得到某个表情的相关解读。
现在我们有个readsmiley脚本和readgivensmiley分别用来随机显示表情符号和显示指定的表情符号。但要更好的模拟smiley命令我们需要将两者功能合而为一。为此,我们的最终脚本需要判断用户有没有提供命令行变量的参数,这一步可以通过shell来完成——参数为空执行readsmiley否则执行readgivensmiley,这应该是相当容易实现的。不过也许你不想在一个shell脚本中使用两个gawk脚本,下面的脚本演示了如何将上面的两个脚本合二为一。为了使之容易理解我将尽量保留原有两个文件的结构和内容:
#!/usr/bin/sh
gawk -vsface="$1" 'BEGIN{
RS=""
FS="\t"
# 当“sface”为null时,则“!sface”为true。
if (sface==""){
RS="\0"
FS="\n[ \t]*\n"
srand()
}
}
!sface { print $int(NF*rand()+1); exit 0}
$1==sface ' mysmiley
将上面的命名为smiley——现在你自己的系统中也有smiley命令了。当你未加参数时,它将随机显示表情符号及它的各种解读。当你使用参数时它将找出该参数对应的各种解读。下面是运行的结果:
hq00e@somewhere ~ $ ./smiley ( :oF Bill Clinton enjoying some French fries hq00e@somewhere ~ $ ./smiley "8-*" 8-* Just ate a hot pepper
附:在Windows的批处理中使用gawk脚本
在Windows的批处理中使用gawk脚本会麻烦些。因为在无法在命令行中跨行使用脚本,同时脚本两端使用的单引号要用双引号替找,而脚本中所使用的双引号之前都要加上转义字符`\‘。像这样的脚本:
gawk 'BEGIN{
var="hello!"
print var }'
在Windows的命令行下应该写为:
gawk "BEGIN{ var=\"hello!\"; print var }"
对于短一点的脚本影响不太,但对长一点的脚本而言就比较麻烦。为了避免这些麻烦,通常的做法是将脚本的部分放到独立的文件中。
在smiley的例子中我们假设脚本被放到名为as.awk中。smiley脚本对应的批处理文件是:
@echo off rem 脚本被存放在as.awk中 gawk -vsface="%1" -f as.awk mysmiley
将上面的内容保存为smiley.bat与as.awk放在同一个目录中。使用示例:
e:\>smiley >;< Butterfly e:\>smiley "3:o" 3:o Smiley calf
[–结束–]
Footnotes
[1] gawk中提供了选项-F在脚本外部定义`FS‘,但FS只接受一个字符的参数。为了在脚本外部定义更复杂点的正则表达式为`FS‘,我们可以用:`gawk -vFS="…"‘的方式定义`FS‘
![hq00e[a]126.com](http://static.flickr.com/56/120355805_7079a475f9_m.jpg)


