2008年11月16日星期日

awk(来自网上)

具体见: 通用线程:Awk 实例,第 2部分

多行记录

正确设置了 FS 变量之后,就可以将 awk 配置成分析几乎任何类型的结构化数据,只要这些数据是每行一个记录。然而,如果要分析占据多行的记录,仅仅依靠设置 FS 是不够的。在这些情况下,我们还需要修改 RS 记录分隔符变量。RS 变量告诉 awk 当前记录什么时候结束,新记录什么时候开始。

譬如,让我们讨论一下如何完成处理“联邦证人保护计划”所涉及人员的地址列表的任务:

Jimmy the Weasel<br />100 Pleasant Drive<br />San Francisco, CA 12345<br />Big Tony<br />200 Incognito Ave.<br />Suburbia, WA 67890<br /></pre></td></tr></tbody></table><br />       <p>理论上,我们希望 awk 将每 3 行看作是一个独立的记录,而不是三个独立的记录。如果 awk 将地址的第一行看作是第一个字段 ($1),街道地址看作是第二个字段 ($2),城市、州和邮政编码看作是第三个字段 $3,那么这个代码就会变得很简单。以下就是我们想要得到的代码: </p>       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">BEGIN {<br />    FS="\n"<br />    RS=""<br />}<br /></pre></td></tr></tbody></table><br />       <p>在上面这段代码中,将 FS 设置成 "\n" 告诉 awk 每个字段都占据一行。通过将 RS 设置成 "",还会告诉 awk 每个地址记录都由空白行分隔。一旦 awk 知道是如何格式化输入的,它就可以为我们执行所有分析工作,脚本的其余部分很简单。让我们研究一个完整的脚本,它将分析这个地址列表,并将每个记录打印在一行上,用逗号分隔每个字段。</p>      <br /><a name="N1007E"><b>address.awk</b></a><br />       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">BEGIN {<br />    FS="\n"<br />    RS=""<br />}<br />{<br />    print $1 ", " $2 ", " $3<br />}<br /></pre></td></tr></tbody></table><br />       <p>如果这个脚本保存为 address.awk,地址数据存储在文件 address.txt 中,可以通过输入 "awk -f address.awk address.txt" 来执行这个脚本。此代码将产生以下输出: </p>       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">Jimmy the Weasel, 100 Pleasant Drive, San Francisco, CA 12345<br />Big Tony, 200 Incognito Ave., Suburbia, WA 67890<br /></pre></td></tr></tbody></table><br />       <br /><table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td><img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%" /><br /><img alt="" src="http://www.ibm.com/i/c.gif" border="0" height="6" width="8" /></td></tr></tbody></table><table class="no-print" cellpadding="0" cellspacing="0" align="right"><tbody><tr align="right"><td><img src="http://www.ibm.com/i/c.gif" alt="" height="4" width="100%" /><br /><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td valign="middle"><br /></td><td valign="top" align="right"><br /></td></tr></tbody></table></td></tr></tbody></table><br /><a name="2"><span class="atitle">OFS 和 ORS</span></a>      <p>在 address.awk 的 print 语句中,可以看到 awk 会连接(合并)一行中彼此相邻的字符串。我们使用此功能在同一行上的三个字段之间插入一个逗号和空格 (", ")。这个方法虽然有用,但比较难看。与其在字段间插入 ", " 字符串,倒不如让通过设置一个特殊 awk 变量 OFS,让 awk 完成这件事。请参考下面这个代码片断。 </p>       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">print "Hello", "there", "Jim!"<br /></pre></td></tr></tbody></table><br />       <p>这行代码中的逗号并不是实际文字字符串的一部分。事实上,它们告诉 awk "Hello"、"there" 和 "Jim!" 是单独的字段,并且应该在每个字符串之间打印 OFS 变量。缺省情况下,awk 产生以下输出: </p>       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">Hello there Jim!<br /></pre></td></tr></tbody></table><br />       <p>这是缺省情况下的输出结果,OFS 被设置成 " ",单个空格。不过,我们可以方便地重新定义 OFS,这样 awk 将插入我们中意的字段分隔符。以下是原始 address.awk 程序的修订版,它使用 OFS 来输出那些中间的 ", " 字符串:</p>      <br /><a name="N100A6"><b>address.awk 的修订版</b></a><br />       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">BEGIN {<br />    FS="\n"<br />    RS=""<br />    OFS=", "<br />}<br />{<br />    print $1, $2, $3<br />}<br /></pre></td></tr></tbody></table><br />       <p>awk 还有一个特殊变量 ORS,全称是“输出记录分隔符”。通过设置缺省为换行 ("\n") 的 OFS,我们可以控制在 print 语句结尾自动打印的字符。缺省 ORS 值会使 awk 在新行中输出每个新的 print 语句。如果想使输出的间隔翻倍,可以将 ORS 设置成 "\n\n"。或者,如果想要用单个空格分隔记录(而不换行),将 ORS 设置成 ""。</p>      <br /><table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td><img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%" /><br /><img alt="" src="http://www.ibm.com/i/c.gif" border="0" height="6" width="8" /></td></tr></tbody></table><table class="no-print" cellpadding="0" cellspacing="0" align="right"><tbody><tr align="right"><td><img src="http://www.ibm.com/i/c.gif" alt="" height="4" width="100%" /><br /><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td valign="middle"><br /></td><td valign="top" align="right"><br /></td></tr></tbody></table></td></tr></tbody></table><br /><a name="3"><span class="atitle">将多行转换成用 tab 分隔的格式</span></a>      <p>假设我们编写了一个脚本,它将地址列表转换成每个记录一行,且用 tab 定界的格式,以便导入电子表格。使用稍加修改的 address.awk 之后,就可以清楚地看到这个程序只适合于三行的地址。如果 awk 遇到以下地址,将丢掉第四行,并且不打印该行: </p>       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">Cousin Vinnie<br />Vinnie's Auto Shop<br />300 City Alley<br />Sosueme, OR 76543<br /></pre></td></tr></tbody></table><br />       <p> 要处理这种情况,代码最好考虑每个字段的记录数量,并依次打印每个记录。现在,代码只打印地址的前三个字段。以下就是我们想要的一些代码:</p>      <br /><a name="N100C3"><b>适合具有任意多字段的地址的 address.awk 版本</b></a><br />       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">BEGIN { <br />    FS="\n" <br />    RS="" <br />    ORS="" <br />} <br /> <br />{  <br />        x=1 <br />        while ( x<NF ) { <br />                print $x "\t" <br />                x++ <br />        } <br />        print $NF "\n" <br />} <br /></pre></td></tr></tbody></table><br />       <p>首先,将字段分隔符 FS 设置成 "\n",将记录分隔符 RS 设置成 "",这样 awk 可以象以前一样正确分析多行地址。然后,将输出记录分隔符 ORS 设置成 "",它将使 print 语句在每个调用结尾         <i>不</i> 输出新行。这意味着如果希望任何文本从新的一行开始,那么需要明确写入         <code>print "\n"</code> 。      </p>      <p>在主代码块中,创建了一个变量 x 来存储正在处理的当前字段的编号。起初,它被设置成 1。然后,我们使用 while 循环(一种 awk 循环结构,等同于 C 语言中的 while 循环),对于所有记录(最后一个记录除外)重复打印记录和 tab 字符。最后,打印最后一个记录和换行;此外,由于将 ORS 设置成 "",print 将不输出换行。程序输出如下,这正是我们所期望的:</p>      <br /><a name="N100DA"><b>我们想要的输出。不算漂亮,但用 tab 定界,以便于导入电子表格</b></a><br />       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">Jimmy the Weasel        100 Pleasant Drive      San Francisco, CA 12345 <br />Big Tony        200 Incognito Ave.      Suburbia, WA 67890<br />Cousin Vinnie   Vinnie's Auto Shop      300 City Alley  Sosueme, OR 76543<br /></pre></td></tr></tbody></table><br />       <br /><table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td><img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%" /><br /><img alt="" src="http://www.ibm.com/i/c.gif" border="0" height="6" width="8" /></td></tr></tbody></table><table class="no-print" cellpadding="0" cellspacing="0" align="right"><tbody><tr align="right"><td><img src="http://www.ibm.com/i/c.gif" alt="" height="4" width="100%" /><br /><br /></td></tr></tbody></table><a name="4"><span class="atitle"><br />循环结构</span></a>      <p>我们已经看到了 awk 的 while 循环结构,它等同于相应的 C 语言 while 循环。awk 还有 "do...while" 循环,它在代码块结尾处对条件求值,而不象标准 while 循环那样在开始处求值。它类似于其它语言中的 "repeat...until" 循环。以下是一个示例:</p>      <br /><a name="N100ED"><b>do...while 示例</b></a><br />       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">{<br />    count=1<br />    do {<br />        print "I get printed at least once no matter what" <br />    } while ( count != 1 )<br />}<br /></pre></td></tr></tbody></table><br />       <p>与一般的 while 循环不同,由于在代码块之后对条件求值,"do...while" 循环永远都至少执行一次。换句话说,当第一次遇到普通 while 循环时,如果条件为假,将永远不执行该循环。</p>      <p>        <b>for 循环</b>        <br />awk 允许创建 for 循环,它就象 while 循环,也等同于 C 语言的 for 循环:      </p>       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">for ( initial assignment; comparison; increment ) {<br />    code block<br />}<br /></pre></td></tr></tbody></table><br />       <p>以下是一个简短示例: </p>       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">for ( x = 1; x <= 4; x++ ) {<br />    print "iteration",x<br />}<br /></pre></td></tr></tbody></table><br />       <p>此段代码将打印: </p>       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">iteration 1<br />iteration 2<br />iteration 3<br />iteration 4<br /></pre></td></tr></tbody></table><br />       <br /><table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td><img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%" /><br /><img alt="" src="http://www.ibm.com/i/c.gif" border="0" height="6" width="8" /></td></tr></tbody></table><table class="no-print" cellpadding="0" cellspacing="0" align="right"><tbody><tr align="right"><td><img src="http://www.ibm.com/i/c.gif" alt="" height="4" width="100%" /><br /><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td valign="middle"><br /></td><td valign="top" align="right"><br /></td></tr></tbody></table></td></tr></tbody></table><a name="5"><span class="atitle"><br />break 和 continue</span></a>      <p>此外,如同 C 语言一样,awk 提供了 break 和 continue 语句。使用这些语句可以更好地控制 awk 的循环结构。以下是迫切需要 break 语句的代码片断:</p>      <br /><a name="N1011D"><b>while 死循环</b></a><br />       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">while (1) {<br />    print "forever and ever..."<br />}<br /></pre></td></tr></tbody></table><br />       <p>while 死循环         <code>1</code> 永远代表是真,这个 while 循环将永远运行下去。以下是一个只执行十次的循环:      </p>      <br /><a name="N1012E"><b>break 语句示例</b></a><br />       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">x=1<br />while(1) {<br />    print "iteration",x<br />    if ( x == 10 ) {<br />        break<br />    }<br />    x++<br />}<br /></pre></td></tr></tbody></table><br />       <p>这里,break 语句用于“逃出”最深层的循环。"break" 使循环立即终止,并继续执行循环代码块后面的语句。</p>      <p>continue 语句补充了 break,其作用如下: </p>       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">x=1<br />while (1) {<br />    if ( x == 4 ) {<br />        x++<br />        continue<br />    }<br />    print "iteration",x<br />    if ( x > 20 ) {<br />        break<br />    }<br />    x++<br />}<br /></pre></td></tr></tbody></table><br />       <p>这段代码打印 "iteration 1" 到 "iteration 21","iteration 4" 除外。如果迭代等于 4,则增加 x 并调用 continue 语句,该语句立即使 awk 开始执行下一个循环迭代,而不执行代码块的其余部分。如同 break 一样,continue 语句适合各种 awk 迭代循环。在 for 循环主体中使用时,continue 将使循环控制变量自动增加。以下是一个等价循环: </p>       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">for ( x=1; x<=21; x++ ) {<br />    if ( x == 4 ) {<br />        continue<br />    }<br />    print "iteration",x<br />}<br /></pre></td></tr></tbody></table><br />       <p>在 while 循环中时,在调用 continue 之前没有必要增加         <code>x</code> ,因为 for 循环会自动增加         <code>x</code> 。      </p>      <br /><table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td><img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%" /><br /><img alt="" src="http://www.ibm.com/i/c.gif" border="0" height="6" width="8" /></td></tr></tbody></table><table class="no-print" cellpadding="0" cellspacing="0" align="right"><tbody><tr align="right"><td><img src="http://www.ibm.com/i/c.gif" alt="" height="4" width="100%" /><br /><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td valign="middle"><br /></td><td valign="top" align="right"><br /></td></tr></tbody></table></td></tr></tbody></table><br /><p><a name="6"><span class="atitle">数组</span></a></p>      <p>如果您知道 awk 可以使用数组,您一定会感到高兴。然而,在 awk 中,数组下标通常从 1 开始,而不是 0: </p>       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">myarray[1]="jim"<br />myarray[2]=456<br /></pre></td></tr></tbody></table><br />       <p>awk 遇到第一个赋值语句时,它将创建         <code>myarray</code> ,并将元素         <code>myarray[1]</code> 设置成 "jim"。执行了第二个赋值语句后,数组就有两个元素了。      </p>      <p>        <b>数组迭代</b>        <br />定义之后,awk 有一个便利的机制来迭代数组元素,如下所示:      </p>       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">for ( x in myarray ) {<br />    print myarray[x]<br />}<br /></pre></td></tr></tbody></table><br />       <p>这段代码将打印数组         <code>myarray</code> 中的每一个元素。当对于 for 使用这种特殊的 "in" 形式时,awk 将         <code>myarray</code>  的每个现有下标依次赋值给         <code>x</code> (循环控制变量),每次赋值以后都循环一次循环代码。虽然这是一个非常方便的 awk 功能,但它有一个缺点 -- 当 awk 在数组下标之间轮转时,它不会依照任何特定的顺序。那就意味着我们不能知道以上代码的输出是:      </p>       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">jim<br />456<br /></pre></td></tr></tbody></table><br />       <p>还是 </p>       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">456<br />jim<br /></pre></td></tr></tbody></table><br />       <p>套用 Forrest Gump 的话来说,迭代数组内容就像一盒巧克力 -- 您永远不知道将会得到什么。因此有必要使 awk 数组“字符串化”,我们现在就来研究这个问题。</p>      <br /><table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td><img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%" /><br /><img alt="" src="http://www.ibm.com/i/c.gif" border="0" height="6" width="8" /></td></tr></tbody></table><br /><p><a name="7"><span class="atitle">数组下标字符串化</span></a></p>      <p>在我的        <a href="http://www.ibm.com/developerworks/cn/linux/shell/awk/awk-1/index.html">前一篇文章</a> 中,我演示了 awk 实际上以字符串格式来存储数字值。虽然 awk 要执行必要的转换来完成这项工作,但它却可以使用某些看起来很奇怪的代码:         <br />      </p>       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">a="1"<br />b="2"<br />c=a+b+3<br /></pre></td></tr></tbody></table><br />       <p>执行了这段代码后,         <code>c</code> 等于         <code>6</code> 。由于 awk 是“字符串化”的,添加字符串 "1" 和 "2" 在功能上并不比添加数字 1 和 2 难。这两种情况下,awk 都可以成功执行运算。awk 的“字符串化”性质非常可爱 -- 您可能想要知道如果使用数组的字符串下标会发生什么情况。例如,使用以下代码:      </p>       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">myarr["1"]="Mr. Whipple"<br />print myarr["1"]<br /></pre></td></tr></tbody></table><br />       <p>可以预料,这段代码将打印 "Mr. Whipple"。但如果去掉第二个 "1" 下标中的引号,情况又会怎样呢? </p>       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">myarr["1"]="Mr. Whipple"<br />print myarr[1]<br /></pre></td></tr></tbody></table><br />       <p>猜想这个代码片断的结果比较难。awk 将         <code>myarr["1"]</code> 和         <code>myarr[1]</code>  看作数组的两个独立元素,还是它们是指同一个元素?答案是它们指的是同一个元素,awk 将打印 "Mr. Whipple",如同第一个代码片断一样。虽然看上去可能有点怪,但 awk 在幕后却一直使用数组的字符串下标!      </p>      <p> 了解了这个奇怪的真相之后,我们中的一些人可能想要执行类似于以下的古怪代码: </p>       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">myarr["name"]="Mr. Whipple"<br />print myarr["name"]<br /></pre></td></tr></tbody></table><br />       <p> 这段代码不仅不会产生错误,而且它的功能与前面的示例完全相同,也将打印 "Mr. Whipple"!可以看到,awk 并没有限制我们使用纯整数下标;如果我们愿意,可以使用字符串下标,而且不会产生任何问题。只要我们使用非整数数组下标,如         <code>myarr["name"]</code> ,那么我们就在使用         <i>关联数组</i> 。从技术上讲,如果我们使用字符串下标,awk 的后台操作并没有什么不同(因为即便使用“整数”下标,awk 还是会将它看作是字符串)。但是,应该将它们称作         <i>关联数组</i> -- 它听起来很酷,而且会给您的上司留下印象。字符串化下标是我们的小秘密。;)      </p>      <br /><table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td><img src="http://www.ibm.com/i/v14/rules/blue_rule.gif" alt="" height="1" width="100%" /><br /><img alt="" src="http://www.ibm.com/i/c.gif" border="0" height="6" width="8" /></td></tr></tbody></table><table class="no-print" cellpadding="0" cellspacing="0" align="right"><tbody><tr align="right"><td><img src="http://www.ibm.com/i/c.gif" alt="" height="4" width="100%" /><br /><table border="0" cellpadding="0" cellspacing="0"><tbody><tr><td valign="middle"><br /></td><td valign="top" align="right"><br /></td></tr></tbody></table></td></tr></tbody></table><p><a name="8"><span class="atitle">数组工具</span></a></p>      <p>谈到数组时,awk 给予我们许多灵活性。可以使用字符串下标,而且不需要连续的数字序列下标(例如,可以定义         <code>myarr[1]</code> 和         <code>myarr[1000]</code> ,但不定义其它所有元素)。虽然这些都很有用,但在某些情况下,会产生混淆。幸好,awk 提供了一些实用功能有助于使数组变得更易于管理。      </p>      <p>首先,可以删除数组元素。如果想要删除数组         <code>fooarray</code>  的元素         <code>1</code> ,输入:      </p>       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">delete fooarray[1]<br /></pre></td></tr></tbody></table><br />       <p>而且,如果想要查看是否存在某个特定数组元素,可以使用特殊的 "in" 布尔运算符,如下所示: </p>       <table border="0" cellpadding="0" cellspacing="0" width="100%"><tbody><tr><td class="code-outline"><pre class="displaycode">if ( 1 in fooarray ) {<br />    print "Ayep!  It's there."<br />} else {<br />    print "Nope!  Can't find it."<br />}<br />

没有评论: