4.7 awk入门
作为一款工具,awk
被设计用于数据流。它非常有趣,其原因就在于它可以对列和行进行操作。awk
有很多内建的功能,比如数组、函数等,这是它和C语言的相同之处。灵活性是awk
最大的优势。
4.7.1 实战演练
awk
脚本的结构基本如下所示:
- awk ' BEGIN{ print "start" } pattern { commands } END{ print "end" }
- file
awk
命令也能够读取stdin
中的内容。
一个awk
脚本通常由3部分组成:BEGIN
语句块、END
语句块和能够使用模式匹配的通用语句块。这3个部分是可选的,它们中任何一个部分都可以不出现在脚本中。脚本通常会被包含在单引号或双引号中:
- awk 'BEGIN { statements } { statements } END { end statements }'
或者也可以使用
- awk "BEGIN { statements } { statements } END { end statements }"
例如:
- $ awk 'BEGIN { i=0 } { i++ } END{ print i}' filename
或者
- $ 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
循环,在循环体中提供了相应的语句。
每读取一行时,它就会检查该行和提供的样式是否匹配。样式本身可以是正则表达式、条件以及行匹配范围等。如果当前行匹配该样式,则执行{ }
中的语句。
样式是可选的。如果没有提供样式,那么它就会默认所有的行都是匹配的,并执行{ }
中的语句。
让我们看看下面的例子:
- $ echo -e "line1\nline2" | awk 'BEGIN{ print "Start" } { print } END{
- print "End" } '
- Start
- line1
- line2
- End
当使用不带参数的print
时,它会打印出当前行。关于print
,需要记住两件重要的事情:当print
的参数是以逗号进行分隔时,参数打印时则以空格作为定界符;在awk
的print
语句中,双引号是被当做拼接操作符(concatenation operator)使用的。
例如:
- $ echo | awk '{ var1="v1"; var2="v2"; var3="v3"; \
- print var1,var2,var3 ; }'
该语句将按照下面的格式打印变量值:
- v1 v2 v3
echo
命令向标准输出写入一行,因此awk
的 { }
语句块中的语句只被执行一次。如果awk
的标准输入包含多行,那么 { }
语句块中的命令就会被执行多次。
拼接的使用方法如下:
- $ echo | awk '{ var1="v1"; var2="v2"; var3="v3"; \
- print var1"-"var2"-"var3 ; }'
输出为:
- v1-v2-v3
{ }
类似于一个循环体,会对文件中的每一行进行迭代。
我们通常将变量初始化语句(如
var=0
)以及打印文件头部的语句放入BEGIN
语句块中。在END{}
语句块中,我们往往会放入打印结果等语句。
4.7.3 补充内容
awk
命令具有丰富的特性。要想洞悉awk
编程的精妙之处,首先应该熟悉awk
重要的选项和功能。让我们来看看awk
的一些重要功能。
- 特殊变量
以下是可以用于awk
的一些特殊变量。
NR
:表示记录数量(number of records),在执行过程中对应于当前行号。NF
:表示字段数量(number of fields),在执行过程中对应于当前行的字段数。$0
:这个变量包含执行过程中当前行的文本内容。$1
:这个变量包含第一个字段的文本内容。$2
:这个变量包含第二个字段的文本内容。
例如:
- $ echo -e "line1 f2 f3\nline2 f4 f5\nline3 f6 f7" | \
- awk '{
- print "Line no:"NR",No of fields:"NF, "$0="$0, "$1="$1,"$2="$2,"$3="$3
- }'
- Line no:1,No of fields:3 $0=line1 f2 f3 $1=line1 $2=f2 $3=f3
- Line no:2,No of fields:3 $0=line2 f4 f5 $1=line2 $2=f4 $3=f5
- Line no:3,No of fields:3 $0=line3 f6 f7 $1=line3 $2=f6 $3=f7
我们可以用print $NF
打印一行中最后一个字段,用 $(NF-1)
打印倒数第二个字段,其他字段依次类推即可。
awk
的printf()
函数的语法和C语言中的同名函数一样。我们也可以用这个函数来代替print
。
再来看awk
的一些基本用法。
打印每一行的第2和第3个字段:
- $awk '{ print $3,$2 }' file
要统计文件中的行数,使用下面的命令:
- $ awk 'END{ print NR }' file
这里只使用了END
语句块。每读入一行,awk
会将NR
更新为对应的行号。当到达最后一行时,NR
中的值就是最后一行的行号,于是,位于END
语句块中的NR
就包含了文件的行数。
你可以将每一行中第一个字段的值按照下面的方法进行累加:
- $ seq 5 | awk 'BEGIN{ sum=0; print "Summation:" }
- { print $1"+"; sum+=$1 } END { print "=="; print sum }'
- Summation:
- 1+
- 2+
- 3+
- 4+
- 5+
- ==
- 15
- 将外部变量值传递给
awk
借助选项 -v
,我们可以将外部值(并非来自stdin)传递给awk
:
- $ VAR=10000
- $ echo | awk -v VARIABLE=$VAR'{ print VARIABLE }'
- 1
还有另一种灵活的方法可以将多个外部变量传递给awk
,例如:
- $ var1="Variable1" ; var2="Variable2"
- $ echo | awk '{ print v1,v2 }' v1=$var1 v2=$var2
- Variable1 Variable2
当输入来自于文件而非标准输入时,使用:
- $ 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
访问文本行的内容。
例如:
- $ seq 5 | awk 'BEGIN { getline; print "Read ahead first line", $0 } { print $0 }'
- Read ahead first line 1
- 2
- 3
- 4
- 5
- 用样式对
awk
处理的行进行过滤
我们可以为需要处理的行指定一些条件,例如:
- $ awk 'NR < 5' # 行号小于5的行
- $ awk 'NR==1,NR==4' #行号在1到5之间的行
- $ awk '/linux/' # 包含样式linux的行(可以用正则表达式来指定样式)
- $ awk '!/linux/' # 不包含包含样式linux的行
- 设置字段定界符
默认的字段定界符是空格。我们也可以用 -F "delimiter"
明确指定一个定界符:
- $ awk -F: '{ print $NF }' /etc/passwd
或者
- awk 'BEGIN { FS=":" } { print $NF }' /etc/passwd
在BEGIN
语句块中则可以用OFS="delimiter"
设置输出字段的定界符。
- 从
awk
中读取命令输出
在下面的代码中,echo
会生成一个空白行。变量cmdout
包含命令grep root /etc/passwd
的输出,然后打印包含root
的行:
将command
的输出读入变量output
的语法如下:
"command" | getline output ;
例如:
- $ echo | awk '{ "grep root /etc/passwd" | getline cmdout ; print cmdout }'
- root:x:0:0:root:/root:/bin/bash
通过使用getline
,能够将外部shell命令的输出读入变量cmdout
。
awk
支持以文本作为索引的关联数组。
- 在
awk
中使用循环
在awk
中可以使用for
循环,其格式如下:
for(i=0;i<10;i++) { print $i ; }
或者
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()
有两个相关的特殊变量,分别是RSTART
和RLENGTH
。变量RSTART
包含正则表达式所匹配内容的起始位置,而变量RLENGTH
包含正则表达式所匹配内容的长度。