Awk 入门/Awk 程序文件
有时 Awk 程序需要反复使用。在这种情况下,从 shell 脚本执行 Awk 程序非常简单。例如,考虑一个将文件中的每个单词打印在单独一行的 Awk 脚本。这可以使用名为“words”的脚本完成,其中包含
awk '{c=split($0, s); for(n=1; n<=c; ++n) print s[n] }' $1
然后可以使“Words”可执行(使用“chmod +x words”),并像任何其他命令一样调用生成的 shell“程序”。例如,
“words”可以从“vi”文本编辑器中调用,如下所示
:%!words
这会将所有文本变成一个单字列表。
- 再举一个例子,考虑前面提到的双倍行距程序。这可以稍微更改以接受标准输入,使用前面描述的“-”,然后复制到名为“double”的文件中
awk '{print; if (NF != 0) print ""}' -
—然后可以从“vi”调用它来对编辑器中的所有文本进行双倍行距。
- 下一步将是允许“double”执行相反的操作:将双倍行距文件恢复为单倍行距,使用选项
undouble
当然,任务的第一部分是设计一种方法来删除多余的空行,而不通过删除所有空行来破坏原始单倍行距文件的间距。最简单的方法是删除连续空行块中的每隔一行。这并不一定能保留原始间距,但它会以某种形式保留间距。
实现此方法也很简单,涉及使用名为“skip”的变量。每当跳过空行时,此变量被设置为“1”,以告诉 Awk 程序不要跳过下一行。方案如下
BEGIN {set skip to 0} scan the input: if skip == 0 if line is blank skip = 1 else print the line get next line of input if skip == 1 print the line skip = 0 get next line of input
这直接转换为以下 Awk 程序
BEGIN {skip = 0} skip == 0 {if (NF == 0) {skip = 1} else {print}; next} skip == 1 {print; skip = 0; next}
该程序可以放在一个单独的文件中,命名为“undouble.awk”,并编写 shell 脚本“undouble”为
awk -f undouble.awk
它也可以直接嵌入到 shell 脚本中,使用单引号将程序括起来,并使用反斜杠(“\”)允许多行
awk 'BEGIN {skip = 0} \ skip == 0 {if (NF == 0) {skip = 1} \ else {print}; \ next} \ skip == 1 {print; \ skip = 0; \ next}'
请记住,当使用“\”将 Awk 程序嵌入到脚本文件中时,该程序在 Awk 中显示为一行。必须使用分号来分隔命令。
再举一个更复杂的例子,我遇到一个问题,当我编写文本文档时,有时我会意外地输入两次同一个词:“结果也是也是那样......”。这些重复的词在校对时很难发现,但编写一个 Awk 程序来完成这项工作很简单,它扫描文本文件以查找重复项;如果找到重复项,则打印重复的词和它所在的行;否则打印“未找到重复项”。
BEGIN { dups=0; w="xy-zzy" } { for( n=1; n<=NF; n++) { if ( w == $n ) { print w, "::", $0 ; dups = 1 } ; w = $n } } END { if (dups == 0) print "No duplicates found." }
“w”变量存储文件中每个词,将其与文件中的下一个词进行比较;w 被初始化为“xy-zzy”,因为这不太可能是文件中的一个词。变量“dup”被初始化为 0,如果找到重复项则设置为 1;如果在结尾时仍然为 0,则程序打印“未找到重复项”消息。与前面的示例一样,我们可以将其放入一个单独的文件中,或者将其嵌入到脚本文件中。
- 最后这些示例使用变量来允许 Awk 程序跟踪它一直在做什么。如前所述,Awk 以循环方式运行:获取一行,处理它,获取下一行,处理它,等等;为了使 Awk 程序记住循环之间的内容,它需要在变量中留下一个便条。
例如,假设我们要匹配第一字段值为 1,000 的行,但随后打印下一行。我们可以这样做:
BEGIN {flag = 0} $1 == 1000 {flag = 1; next} flag == 1 {print; flag = 0; next}
该程序在找到以 1,000 开头的行时设置一个名为“flag”的变量,然后获取下一行输入。打印下一行输入,然后清除“flag”,以便下一行不会被打印。
如果我们想打印接下来的五行,我们可以使用一个名为“counter”的变量以几乎相同的方式做到这一点
BEGIN {counter = 0} $1 == 1000 {counter = 5; next} counter > 0 {print; counter--; next}
该程序在找到以 1,000 开头的行时将名为“counter”的变量初始化为 5;对于接下来的 5 行输入,它打印它们并将“counter”递减,直到它为零。
这种方法可以根据需要进行扩展。假设我们有一个输入五行的五种不同操作的列表,要在匹配一行输入后执行;然后我们可以创建一个名为“state”的变量,它存储接下来要执行的列表中的哪个项目。该方案通常如下
BEGIN {set state to 0} scan the input: if match set state to 1 get next line of input if state == 1 do the first thing in the list state = 2 get next line of input if state == 2 do the second thing in the list state = 3 get next line of input if state == 3 do the third thing in the list state = 4 get next line of input if state == 4 do the fourth thing in the list state = 5 get next line of input if state == 5 do the fifth (and last) thing in the list state = 0 get next line of input
这被称为“状态机”。在这种情况下,它正在执行一个简单的操作列表,但相同的方法也可以用于执行更复杂的动作分支序列,就像我们在流程图中而不是简单的列表中可能遇到的那样。
我们可以将状态号分配给流程图中的块,然后使用 if-then 测试来设置状态变量,以指示接下来应该执行哪个备用操作。但是,很少有 Awk 程序需要如此复杂,在这里介绍更详细的示例可能会比它值得的更令人困惑。需要记住的重要一点是,awk 程序可以在一行扫描循环中在变量中给自己留言,以告诉它在以后的行扫描循环中该做什么。