一、使用规则 awk 适合于文本处理和报表生成,它还有许多精心设计的特性,容许进行须要特殊技巧程序设计。 awk 的语法较为常见。它借鉴了某些语言的一些精华部分,如C 语言、python 和 bash。 第一个 awk 让咱们继续,开始使用 awk,以了解其工做原理。在命令行中输入如下命令: $ awk '{ print }' /etc/passwd 您将会见到 /etc/passwd 文件的内容出如今眼前。如今,解释 awk 作了些什么。调用 awk 时,咱们指定 /etc/passwd 做为输入文件。执行 awk 时,它依次对 /etc/passwd 中的每一行执行 print 命令。全部输出都发送到 stdout,所获得的结果与与执行catting /etc/passwd彻底相同。 如今,解释 { print } 代码块。在 awk 中,花括号用于将几块代码组合到一块儿,这一点相似于 C 语言。在代码块中只有一条 print 命令。在 awk 中,若是只出现 print 命令,那么将打印当前行的所有内容。 这里是另外一个awk 示例,做用与上例彻底相同: $ awk '{ print $0 }' /etc/passwd 在 awk 中,$0 变量表示整个当前行,因此 print 和 print $0 的做用彻底同样。 建立一个 awk 程序,让它输出与输入数据彻底无关的数据。 示例1: $ awk '{ print "" }' /etc/passwd 只要将 "" 字符串传递给 print 命令,它就会打印空白行。测试该脚本,将会发现对于/etc/passwd文件中的每一行,awk 都输出一个空白行。由此可知,awk对输入文件中的每一行都执行这个脚本。 示例2: $ awk '{ print "hiya" }' /etc/passwd 运行此脚本将在您的屏幕上写满 hiya。 二、处理多个字段 awk 很是善于处理分红多个逻辑字段的文本,还能够引用 awk 脚本中每一个独立的字段。 打印系统上全部用户账户的列表: $ awk -F":" '{ print $1 }' /etc/passwd 上例中,调用awk时,使用 -F 选项来指定 ":" 做为字段分隔符。awk 处理 print $1 命令时,它会打印出在输入文件中每一行中出现的第一个字段。 如下是另外一示例: $ awk -F":" '{ print $1 $3 }' /etc/passwd 如下是该脚本输出的摘录: halt7 operator11 root0 shutdown6 sync5 bin1 ....etc. 如您所见,awk 打印出 /etc/passwd 文件的第一和第三个字段,它们正好分别是用户名和用户标识字段。如今,当脚本运行时,它并不理想--在两个输出字段之间没有空格!若是习惯于使用 bash 或 python 进行编程,那么您会期望 print $1 $3 命令在两个字段之间插入空格。然而,当两个字符串在 awk 程序中彼此相邻时,awk 会链接它们但不在它们之间添加空格。如下命令会在这两个字段中插入空格: $ awk -F":" '{ print $1 " " $3 }' /etc/passwd 以这种方式调用 print 时,它将链接 $一、" " 和 $3,建立可读的输出。 还能够插入一些文本标签: $ awk -F":" '{ print "username: " $1 "ttuid:" $3" }' /etc/passwd 这将产生如下输出: username: halt uid:7 username: operator uid:11 username: root uid:0 username: shutdown uid:6 username: sync uid:5 username: bin uid:1 ....etc. 三、调用外部脚本 将脚本做为命令行自变量传递给awk对于小的单行程序来讲很简单。 而对于多行程序,则能够在外部文件中撰写脚本,而后向awk传递-f选项,以向它提供外部脚本文件的调用: $ awk -f myscript.awk myfile.in 将脚本放入文本文件还可使用附加awk功能。例如: BEGIN { FS=":" } { print $1 } 打印出 /etc/passwd 中每一行的第一个字段 在这个脚本中,字段分隔符在代码自身中指定(经过设置 FS 变量)。 在脚本自身中设置字段分隔符,能够少输入一个命令行自变量。 四、begin和end块 BEGIN 和 END 块 一般,对于每一个输入行,awk 都会执行每一个脚本代码块一次。然而,可能须要在 awk 开始处理输入文件中的文本以前执行初始化代码。对于这种状况,awk 容许您定义一个 BEGIN 块。咱们在前一个示例中使用了 BEGIN 块。由于 awk 在开始处理输入文件以前会执行 BEGIN 块,所以它是初始化 FS(字段分隔符)变量、打印页眉或初始化其它在程序中之后会引用的全局变量的极佳位置。 awk 还提供了另外一个特殊块,叫做 END 块。awk 在处理了输入文件中的全部行以后执行这个块。一般,END 块用于执行最终计算或打印应该出如今输出流结尾的摘要信息。 五、正则表达式 awk 容许使用正则表达式,根据正则表达式是否匹配当前行来选择执行独立代码块。 输出包含字符序列foo的行: /foo/ { print } 复杂点的,只打印包含浮点数的行: /[0-9]+.[0-9]*/ { print } 能够将任意一种布尔表达式放在一个代码块以前,以控制什么时候执行某特定块。仅当对前面的布尔表达式求值为真时,awk 才执行代码块。如下示例脚本输出将输出其第一个字段等于 fred 的全部行中的第三个字段。若是当前行的第一个字段不等于 fred,awk 将继续处理文件而不对当前行执行 print 语句: $1 == "fred" { print $3 } awk 提供了完整的比较运算符集合,包括 "=="、"<"、">"、"<="、">=" 和 "!="。另外,awk 还提供了 "~" 和 "!~" 运算符,它们分别表示“匹配”和“不匹配”。 它们的用法是在运算符左边指定变量,在右边指定正则表达式。若是某一行的第五个字段包含字符序列 root,如下示例只打印这一行中的第三个字段: $5 ~ /root/ { print $3 } 六、条件语句 awk 还提供了很是好的相似于 C 语言的 if 语句。if 语句示例: { if ( $5 ~ /root/ ) { print $3 } } 对每个输入行执行代码块,使用 if 语句来选择执行 print 命令。 更复杂的 awk if 语句示例。 { if ( $1 == "foo" ) { if ( $2 == "foo" ) { print "uno" } else { print "one" } } else if ($1 == "bar" ) { print "two" } else { print "three" } } 使用 if 语句还能够将代码: ! /matchme/ { print $1 $3 $4 } 转换成: { if ( $0 !~ /matchme/ ) { print $1 $3 $4 } } 这两个脚本都只输出不包含 matchme 字符序列的那些行。 awk 还容许使用布尔运算符 "||"(逻辑与)和 "&&"(逻辑或),以便建立更复杂的布尔表达式: ( $1 == "foo" ) && ( $2 == "bar" ) { print } 这个示例只打印第一个字段等于 foo 且第二个字段等于 bar 的行。 七、变量 awk的变量,数值变量与字符串变量。 数值变量 至今,咱们不是打印字符串、整行就是特定字段。然而,awk还能够执行整数和浮点运算。使用数学表达式,能够很方便地编写计算文件中空白行数量的脚本。 BEGIN { x=0 } /^$/ { x=x+1 } END { print "I found " x " blank lines. :}" } 在 BEGIN 块中,将整数变量 x 初始化成零。而后,awk 每次遇到空白行时,awk 将执行 x=x+1 语句,递增 x。 处理完全部行以后,执行 END 块,awk 将打印出最终摘要,指出它找到的空白行数量。 字符串化变量 awk 的优势之一就是“简单和字符串化”。我认为 awk 变量“字符串化”是由于全部 awk 变量在内部都是按字符串形式存储的。同时,awk 变量是“简单的”,由于能够对它执行数学操做,且只要变量包含有效数字字符串,awk 会自动处理字符串到数字的转换步骤。要理解个人观点,请研究如下示例: x="1.01" # We just set x to contain the *string* "1.01" x=x+1 # We just added one to a *string* print x # Incidentally, these are comments :) awk 将输出: 2.01 虽然将字符串值 1.01 赋值给变量 x,仍然能够对它加一。但在 bash 和 python 中却不能这样作。 首先,bash 不支持浮点运算。并且,若是 bash 有“字符串化”变量,它们并不“简单”;要执行任何数学操做,bash 要求咱们将数字放到丑陋的 $( ) ) 结构中。 若是使用 python,则必须在对 1.01 字符串执行任何数学运算以前,将它转换成浮点值。虽然这并不困难,但它还是附加的步骤。 若是使用 awk,它是全自动的,而那会使咱们的代码又好又整洁。若是想要对每一个输入行的第一个字段乘方并加一,可使用如下脚本: { print ($1^2)+1 } 若是作一个小实验,就能够发现若是某个特定变量不包含有效数字,awk 在对数学表达式求值时会将该变量看成数字零处理。 八、运算符 awk 有完整的数学运算符集合。除了标准的加、减、乘、除,awk 还容许使用前面演示过的指数运算符 "^"、模(余数)运算符 "%" 和其它许多从 C 语言中借入的易于使用的赋值操做符。 这些运算符包括先后加减(i++、--foo)、加/减/乘/除赋值运算符( a+=三、b*=二、c/=2.二、d-=6.2)。不只如此 -- 咱们还有易于使用的模/指数赋值运算符(a^=二、b%=4)。 字段分隔符 awk 有它本身的特殊变量集合。其中一些容许调整 awk 的运行方式,而其它变量能够被读取以收集关于输入的有用信息。咱们已经接触过这些特殊变量中的一个,FS。前面已经提到过,这个变量让您能够设置 awk 要查找的字段之间的字符序列。咱们使用 /etc/passwd 做为输入时,将 FS 设置成 ":"。当这样作有问题时,咱们还能够更灵活地使用 FS。 FS 值并无被限制为单一字符;能够经过指定任意长度的字符模式,将它设置成规则表达式。若是正在处理由一个或多个 tab 分隔的字段,您可能但愿按如下方式设置 FS: FS="t+" 以上示例中,咱们使用特殊 "+" 规则表达式字符,它表示“一个或多个前一字符”。 若是字段由空格分隔(一个或多个空格或 tab),您可能想要将 FS 设置成如下规则表达式: FS="[[:space:]+]" 这个赋值表达式也有问题,它并不是必要。为何?由于缺省状况下,FS 设置成单一空格字符,awk 将这解释成表示“一个或多个空格或 tab”。在这个特殊示例中,缺省 FS 设置偏偏是您最想要的! 复杂的规则表达式也不成问题。即便您的记录由单词 "foo" 分隔,后面跟着三个数字,如下规则表达式仍容许对数据进行正确的分析: FS="foo[0-9][0-9][0-9]" 字段数量 接着咱们要讨论的两个变量一般并非须要赋值的,而是用来读取以获取关于输入的有用信息。第一个是 NF 变量,也叫作“字段数量”变量。awk 会自动将该变量设置成当前记录中的字段数量。可使用 NF 变量来只显示某些输入行: NF == 3 { print "this particular record has three fields: " $0 } 固然,也能够在条件语句中使用 NF 变量,以下: { if ( NF > 2 ) { print $1 " " $2 ":" $3 } } 九、处理记录 记录号 记录号 (NR) 是另外一个方便的变量。它始终包含当前记录的编号(awk 将第一个记录算做记录号 1)。迄今为止,咱们已经处理了每一行包含一个记录的输入文件。对于这些状况,NR 还会告诉您当前行号。然而,当咱们在本系列之后部分中开始处理多行记录时,就不会再有这种状况,因此要注意!能够象使用 NF 变量同样使用 NR 来只打印某些输入行: (NR < 10 ) || (NR > 100) { print "We are on record number 1-9 or 101+" } 另外一个示例: { #skip header if (NR>10) { print "ok, now for the real information!" } } awk 提供了适合各类用途的附加变量。咱们将在之后的文章中讨论这些变量。 多行记录 awk 是一种用于读取和处理结构化数据(如系统的 /etc/passwd 文件)的极佳工具。/etc/passwd 是 UNIX 用户数据库,而且是用冒号定界的文本文件,它包含许多重要信息,包括全部现有用户账户和用户标识,以及其它信息。在个人前一篇文章中,我演示了 awk 如何轻松地分析这个文件。咱们只须将 FS(字段分隔符)变量设置成 ":"。 正确设置了 FS 变量以后,就能够将 awk 配置成分析几乎任何类型的结构化数据,只要这些数据是每行一个记录。然而,若是要分析占据多行的记录,仅仅依靠设置 FS 是不够的。在这些状况下,咱们还须要修改 RS 记录分隔符变量。RS 变量告诉 awk 当前记录何时结束,新记录何时开始。 譬如,让咱们讨论一下如何完成处理“联邦证人保护计划”所涉及人员的地址列表的任务: Jimmy the Weasel 100 Pleasant Drive San Francisco, CA 12345 Big Tony 200 Incognito Ave. Suburbia, WA 67890 理论上,咱们但愿 awk 将每 3 行看做是一个独立的记录,而不是三个独立的记录。若是 awk 将地址的第一行看做是第一个字段 ($1),街道地址看做是第二个字段 ($2),城市、州和邮政编码看做是第三个字段 $3,那么这个代码就会变得很简单。代码以下: BEGIN { FS="n" RS="" } 在上面这段代码中,将 FS 设置成 "n" 告诉 awk 每一个字段都占据一行。经过将 RS 设置成 "",还会告诉 awk 每一个地址记录都由空白行分隔。一旦 awk 知道是如何格式化输入的,它就能够为咱们执行全部分析工做,脚本的其他部分很简单。让咱们研究一个完整的脚本,它将分析这个地址列表,并将每一个记录打印在一行上,用逗号分隔每一个字段。 address.awk BEGIN { FS="n" RS="" } { print $1 ", " $2 ", " $3 } 将脚本保存为 address.awk,地址数据存储在文件 address.txt 中,能够经过输入 "awk -f address.awk address.txt" 执行此脚本。输出以下: Jimmy the Weasel, 100 Pleasant Drive, San Francisco, CA 12345 Big Tony, 200 Incognito Ave., Suburbia, WA 67890 OFS 和 ORS 在 address.awk 的 print 语句中,能够看到 awk 会链接(合并)一行中彼此相邻的字符串。咱们使用此功能在同一行上的三个字段之间插入一个逗号和空格 (", ")。这个方法虽然有用,但比较难看。与其在字段间插入 ", " 字符串,倒不如让经过设置一个特殊 awk 变量 OFS,让 awk 完成这件事。 print "Hello", "there", "Jim!" 这行代码中的逗号并非实际文字字符串的一部分。事实上,它们告诉 awk "Hello"、"there" 和 "Jim!" 是单独的字段,而且应该在每一个字符串之间打印 OFS 变量。 缺省状况下,awk 产生如下输出: Hello there Jim! 这是缺省状况下的输出结果,OFS 被设置成 " ",单个空格。不过,咱们能够方便地从新定义 OFS,这样 awk 将插入咱们中意的字段分隔符。如下是原始 address.awk 程序的修订版,它使用 OFS 来输出那些中间的 ", " 字符串: address.awk 的修订版 BEGIN { FS="n" RS="" OFS=", " } { print $1, $2, $3 } awk 还有一个特殊变量 ORS,全称是“输出记录分隔符”。经过设置缺省为换行 ("n") 的 OFS,咱们能够控制在 print 语句结尾自动打印的字符。缺省 ORS 值会使 awk 在新行中输出每一个新的 print 语句。若是想使输出的间隔翻倍,能够将 ORS 设置成 "nn"。或者,若是想要用单个空格分隔记录(而不换行),将 ORS 设置成 " "。 将多行转换成用 tab 分隔的格式 假设咱们编写了一个脚本,它将地址列表转换成每一个记录一行,且用 tab 定界的格式,以便导入电子表格。使用稍加修改的 address.awk 以后,就能够清楚地看到这个程序只适合于三行的地址。若是 awk 遇到如下地址,将丢掉第四行,而且不打印该行: Cousin Vinnie Vinnie's Auto Shop 300 City Alley Sosueme, OR 76543 要处理这种状况,代码最好考虑每一个字段的记录数量,并依次打印每一个记录。如今,代码只打印地址的前三个字段。如下就是咱们想要的一些代码: 适合具备任意多字段的地址的 address.awk 版本 BEGIN { FS="n" RS="" ORS="" } { x=1 while ( x<NF ) { print $x "t" x++ } print $NF "n" } 首先,将字段分隔符 FS 设置成 "n",将记录分隔符 RS 设置成 "",这样 awk 能够象之前同样正确分析多行地址。而后,将输出记录分隔符 ORS 设置成 "",它将使 print 语句在每一个调用结尾不输出新行。这意味着若是但愿任何文本重新的一行开始,那么须要明确写入 print "n"。 在主代码块中,建立了一个变量 x 来存储正在处理的当前字段的编号。起初,它被设置成 1。而后,咱们使用 while 循环(一种 awk 循环结构,等同于 C 语言中的 while 循环),对于全部记录(最后一个记录除外)重复打印记录和 tab 字符。最后,打印最后一个记录和换行;此外,因为将 ORS 设置成 "",print 将不输出换行。程序输出以下,这正是咱们所指望的(不算漂亮,但用 tab 定界,以便于导入电子表格): Jimmy the Weasel 100 Pleasant Drive San Francisco, CA 12345 Big Tony 200 Incognito Ave. Suburbia, WA 67890 Cousin Vinnie Vinnie's Auto Shop 300 City Alley Sosueme, OR 76543