4.7 awk入门

作为一款工具,awk被设计用于数据流。它非常有趣,其原因就在于它可以对列和行进行操作。awk有很多内建的功能,比如数组、函数等,这是它和C语言的相同之处。灵活性是awk最大的优势。

4.7.1 实战演练

awk脚本的结构基本如下所示:

  1. awk ' BEGIN{ print "start" } pattern { commands } END{ print "end" }
  2. file

awk命令也能够读取stdin中的内容。

一个awk脚本通常由3部分组成:BEGIN语句块、END语句块和能够使用模式匹配的通用语句块。这3个部分是可选的,它们中任何一个部分都可以不出现在脚本中。脚本通常会被包含在单引号或双引号中:

  1. awk 'BEGIN { statements } { statements } END { end statements }'

或者也可以使用

  1. awk "BEGIN { statements } { statements } END { end statements }"

例如:

  1. $ awk 'BEGIN { i=0 } { i++ } END{ print i}' filename

或者

  1. $ awk "BEGIN { i=0 } { i++ } END{ print i }" filename

4.7.2 工作原理

awk命令的工作方式如下所示。

  • 执行BEGIN { commands } 语句块中的语句。

  • 从文件或stdin中读取一行,然后执行pattern { commands }。重复这个过程,直到文件全部被读取完毕。

  • 当读至输入流(input stream)末尾时,执行END { commands } 语句块。

BEGIN语句块在awk开始从输入流中读取行之前被执行。这是一个可选的语句块,诸如变量初始化、打印输出表格的表头等语句通常都可以写入BEGIN语句块中。

END语句块和BEGIN语句块类似。END语句块在awk从输入流中读取完所有的行之后即被执行。像打印所有行的分析结果这类汇总信息,都是在END语句块中实现的常见任务(例如,在比较过所有的行之后,打印出最大数)。它也是一个可选的语句块。

最重要的部分就是pattern语句块中的通用命令。这个语句块同样是可选的。如果不提供该语句块,则默认执行{ print },即打印每一个读取到的行。awk对于读取的每一行,都会执行这个语句块。

这就像一个用来读取行的while循环,在循环体中提供了相应的语句。

每读取一行时,它就会检查该行和提供的样式是否匹配。样式本身可以是正则表达式、条件以及行匹配范围等。如果当前行匹配该样式,则执行{ }中的语句。

样式是可选的。如果没有提供样式,那么它就会默认所有的行都是匹配的,并执行{ }中的语句。

让我们看看下面的例子:

  1. $ echo -e "line1\nline2" | awk 'BEGIN{ print "Start" } { print } END{
  2. print "End" } '
  3. Start
  4. line1
  5. line2
  6. End

当使用不带参数的print时,它会打印出当前行。关于print,需要记住两件重要的事情:当print的参数是以逗号进行分隔时,参数打印时则以空格作为定界符;在awkprint语句中,双引号是被当做拼接操作符(concatenation operator)使用的。

例如:

  1. $ echo | awk '{ var1="v1"; var2="v2"; var3="v3"; \
  2. print var1,var2,var3 ; }'

该语句将按照下面的格式打印变量值:

  1. v1 v2 v3

echo命令向标准输出写入一行,因此awk{ } 语句块中的语句只被执行一次。如果awk的标准输入包含多行,那么 { } 语句块中的命令就会被执行多次。

拼接的使用方法如下:

  1. $ echo | awk '{ var1="v1"; var2="v2"; var3="v3"; \
  2. print var1"-"var2"-"var3 ; }'

输出为:

  1. v1-v2-v3

{ }类似于一个循环体,会对文件中的每一行进行迭代。

4.7 awk入门 - 图1 我们通常将变量初始化语句(如var=0)以及打印文件头部的语句放入BEGIN语句块中。在END{}语句块中,我们往往会放入打印结果等语句。

4.7.3 补充内容

awk命令具有丰富的特性。要想洞悉awk编程的精妙之处,首先应该熟悉awk重要的选项和功能。让我们来看看awk的一些重要功能。

  • 特殊变量

以下是可以用于awk的一些特殊变量。

  • NR:表示记录数量(number of records),在执行过程中对应于当前行号。

  • NF:表示字段数量(number of fields),在执行过程中对应于当前行的字段数。

  • $0:这个变量包含执行过程中当前行的文本内容。

  • $1:这个变量包含第一个字段的文本内容。

  • $2:这个变量包含第二个字段的文本内容。

例如:

  1. $ echo -e "line1 f2 f3\nline2 f4 f5\nline3 f6 f7" | \
  2.  
  3. awk '{
  4. print "Line no:"NR",No of fields:"NF, "$0="$0, "$1="$1,"$2="$2,"$3="$3
  5. }'
  6. Line no:1,No of fields:3 $0=line1 f2 f3 $1=line1 $2=f2 $3=f3
  7. Line no:2,No of fields:3 $0=line2 f4 f5 $1=line2 $2=f4 $3=f5
  8. Line no:3,No of fields:3 $0=line3 f6 f7 $1=line3 $2=f6 $3=f7

我们可以用print $NF打印一行中最后一个字段,用 $(NF-1)打印倒数第二个字段,其他字段依次类推即可。

awkprintf()函数的语法和C语言中的同名函数一样。我们也可以用这个函数来代替print

再来看awk的一些基本用法。

打印每一行的第2和第3个字段:

  1. $awk '{ print $3,$2 }' file

要统计文件中的行数,使用下面的命令:

  1. $ awk 'END{ print NR }' file

这里只使用了END语句块。每读入一行,awk会将NR更新为对应的行号。当到达最后一行时,NR中的值就是最后一行的行号,于是,位于END语句块中的NR就包含了文件的行数。

你可以将每一行中第一个字段的值按照下面的方法进行累加:

  1. $ seq 5 | awk 'BEGIN{ sum=0; print "Summation:" }
  2. { print $1"+"; sum+=$1 } END { print "=="; print sum }'
  3. Summation:
  4. 1+
  5. 2+
  6. 3+
  7. 4+
  8. 5+
  9. ==
  10. 15
  • 将外部变量值传递给awk

借助选项 -v,我们可以将外部值(并非来自stdin)传递给awk

  1. $ VAR=10000
  2. $ echo | awk -v VARIABLE=$VAR'{ print VARIABLE }'
  3. 1

还有另一种灵活的方法可以将多个外部变量传递给awk,例如:

  1. $ var1="Variable1" ; var2="Variable2"
  2. $ echo | awk '{ print v1,v2 }' v1=$var1 v2=$var2
  3. Variable1 Variable2

当输入来自于文件而非标准输入时,使用:

  1. $ awk '{ print v1,v2 }' v1=$var1 v2=$var2 filename

在上面的方法中,变量之间用空格分隔,以键-值对的形式(v1=$var1 v2=$var2)作为awk的命名行参数紧随在BEGIN{}END语句块之后。

  • getline读取行

通常,grep默认读取一个文件的所有行。如果只想读取某一行,可以使用getline函数。有时候,我们需要从BEGIN语句块中读取第一行。

语法:getline var

变量var就包含了特定行的内容。

如果调用不带参数的getline,我们可以用 $0$1$2访问文本行的内容。

例如:

  1. $ seq 5 | awk 'BEGIN { getline; print "Read ahead first line", $0 } { print $0 }'
  2. Read ahead first line 1
  3. 2
  4. 3
  5. 4
  6. 5
  • 用样式对awk处理的行进行过滤

我们可以为需要处理的行指定一些条件,例如:

  1. $ awk 'NR < 5' # 行号小于5的行
  2. $ awk 'NR==1,NR==4' #行号在1到5之间的行
  3. $ awk '/linux/' # 包含样式linux的行(可以用正则表达式来指定样式)
  4. $ awk '!/linux/' # 不包含包含样式linux的行
  • 设置字段定界符

默认的字段定界符是空格。我们也可以用 -F "delimiter"明确指定一个定界符:

  1. $ awk -F: '{ print $NF }' /etc/passwd

或者

  1. awk 'BEGIN { FS=":" } { print $NF }' /etc/passwd

BEGIN语句块中则可以用OFS="delimiter"设置输出字段的定界符。

  • awk中读取命令输出

在下面的代码中,echo会生成一个空白行。变量cmdout包含命令grep root /etc/passwd的输出,然后打印包含root的行:

command的输出读入变量output的语法如下:

  1. "command" | getline output ;

例如:

  1. $ echo | awk '{ "grep root /etc/passwd" | getline cmdout ; print cmdout }'
  2. root:x:0:0:root:/root:/bin/bash

通过使用getline,能够将外部shell命令的输出读入变量cmdout

awk支持以文本作为索引的关联数组。

  • awk中使用循环

awk中可以使用for循环,其格式如下:

  1. for(i=0;i<10;i++) { print $i ; }

或者

  1. for(i in array) { print array[i]; }

awk有很多内建的字符串控制函数,让我们认识一下其中部分函数。

  • length(string):返回字符串的长度。

  • index(string, search_string):返回search_string在字符串中出现的位置。

  • split(string, array, delimiter):用定界符生成一个字符串列表,并将该列表存入数组。

  • substr(string, start-position, end-position):在字符串中用字符起止偏移量生成子串,并返回该子串。

  • sub(regex, replacement_str, string):将正则表达式匹配到的第一处内容替换成replacment_str

  • gsub(regex, replacment_str, string):和sub()类似。不过该函数会替换正则表达式匹配到的所有内容。

  • match(regex, string):检查正则表达式是否能够匹配字符串。如果能够匹配,返回非0值;否则,返回0。match()有两个相关的特殊变量,分别是RSTARTRLENGTH。变量RSTART包含正则表达式所匹配内容的起始位置,而变量RLENGTH包含正则表达式所匹配内容的长度。