AWK教程
1. 什么是awk
awk 以逐行方式扫描文件(或输入),从第一行到最后一行,以查找匹配某个特定模式的文本行,并对这些文本行执行(括在花括号中的)指定动作。如果只给出模式而未指定动作,则所有匹配该模式的行都显示在屏幕上;如果只指定动作而未定义模式,会对所有输入行执行指定动作。
2. awk的格式
awk 指令由模式、操作、或模式与操作的组合组成。模式是由某种类型的表达式组成的语句。如果某个表达式中没有出现关键字if,但实际计算时却暗含if这个词,那么,这个表达式就是模式。操作由括在大括号内的一条或多条语句组成,语句之间用分号或换行符隔开,模式则不能被括在大括号中,模式由括在两个正斜杠之间的正则表达式、一个或多个awk 操作符组成的表达式组成。
以下三种模式,仅操作、仅匹配模式、匹配模式和操作:
$awk '{action}' fi1ename
$awk 'pattern' filename
$awk 'pattern {action}' fi1ename
范例:
$awk '/Mary/' employees
Mary Adams 5346 11/4/63 28765
$awk '{print $1}' employees
Tom
Mary
Sally
Billy
$awk '/Sally/{print $1,$2}' employees
Sally Chang
可以将一条或多条Linux命令的输出通过管道发给awk处理。格式如下:
$command | awk 'pattern'
$command | awk '{action}'
$command | awk 'pattern {action}'
范例:
$cat employees | awk '/Sally/{print $1}'
Sally Chang
3. awk工作原理
1.awk使用一行作为输入(通过文件或者管道),并将这一行赋给内部变量$0 ,默认时每一行也可以称为一个记录,以换行符结束。 2.然后,行被空格分解成字段(单词),每一个字段存储在已经编号的变量中,从$1 开始,可以多达100 个字段。 3.awk如何知道空格是用来分隔字段的呢?因为有另一个内部变量FS用来确定字段的分隔符。初始时,FS被赋为空格(包含制表符和空格符)。如果需要使用其他的字符分隔字段,如冒号或破折号,则需要将FS 变量的值设为新的字段分隔符。
4.awk打印字段时,将以下面方式使用print函数。
{print $1,$3}
Tom 100
Molly 200
John 300
awk在Tom和100之间加入了空格,因为在$1和$3之间存在一个逗号。逗号比较特殊,它映射为另一个内部变量,称为输出字段分隔符(OFS), OFS默认为空格。逗号被OFS变量中存储的字符替换。
5.awk输出之后,将从文件中获取另一行,并将其存储到$0中,覆盖原来的内容,然后将新的字符串分隔成字段并进行处理。这个过程将持续到整个文件的所有行都处理完毕。
4. print函数
awk命令的操作部分被括在大括号内。如果未指定操作,则匹配到模式时, awk 会采取默认操作,即在屏幕上打印包含模式的行。print函数用于简单的输出。更为复杂的输出则要使用printf和sprintf 函数。如果熟悉C语言,那么一定懂得如何使用printf和sprintf。
也可以用{print}形式在awk命令的动作部分显式地调用print函数。print函数的参数可以是变量、数值或字符串常量。字符串必须用双引号括起来。参数之间用逗号分隔,如果没有逗号,所有的参数就会被串在一起。逗号等价于OFS中的值,默认情况下是空格。
print函数的输出可以被重定向,也可以通过管道传给另一个程序。其他程序的输出也可以通过管道交给awk打印。 范例:
$ date
Thu Mar 12 12:23:23 CST 2015
$ date | awk '{print "Month: "$2 "\nYear: "$6}'
Month: Mar
Year: 2015
说明:Linux中, date命令的输出经管道发送给awk。打印显示为字符串Month:,后面跟date输出结果中的第2个字段,然后是另一个字符串,该串中包含换行符\n和Year:,最后是date输出结果中的第6个字段($6)。
转义序列:转义序列用一个反斜杠后跟一个字母或数字来表示。它们可以用在字符申中,代表制表符、换行符、换页符等(参见下表) 。 |转义序列|含义| | :-------- | --------:| :--: | |\b |退格| |\f |换页| |\n |换行| |\r |回车| |\t |制表符| |\047|八进制值47. 即单引号 |\c |C 代表任一其他字符,例如"" 范例:
$ cat employees
Tome Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500
$ awk '/Sally/{print "\t\tHave a nice day," $1,$2 "\!"}' employees
Have a nice day,Sa11y Chang!
说明:如果某一行包含模式Sally,print函数就会打印出两个制表符,字符串"Have a nice day",该行的第一个字段Sally和第二个字段Chang,后面跟一个带感叹号的字符串。
5. OFMT变量
打印数字时,可能需要控制数字的格式。这可以通过printf函数来实现,但是,通过设置一个特殊的awk变量OFMT,使用print函数时也可以控制数字的打印格式。OFMT的默认值是"%.6gd",表示只打印小数部分的前6 位(之后我们会介绍如何修改OFMT的值)。
$ awk 'BEGIN{OFMT="%.2f";print 1.2567,12E-2}'
1.25 0.12
说明:如果设置了变量OFMT,在打印浮点数时,就只打印小数部分的前两位。百分号(%)表示接下来要定义格式。
6. printf函数
打印输出时,可能需要指定字段间的空格数,从而把列排整齐。在print函数中使用制表符并不能保证得到想要的输出,因此,可以用printf函数来格式化特别的输出。
printf函数返回一个带格式的字符串给标准输出,如同C语言中的printf语句一样。printf语句包括一个加引号的控制串,控制串中可能嵌有若干格式说明和修饰符。控制串后面跟一个逗号,之后是一列由逗号分隔的表达式。printf函数根据控制串中的说明编排这些表达式的格式。与print函数不同的是, printf不会在行尾自动换行。因此,如果要换行,就必须在控制串中提供转义字符\n。
每一个百分号和格式说明都必须有一个对应的变量。要打印百分号就必须在控制串中给出两个百分号。请参考print转义字符和printf修饰符。格式说明由百分号引出,另外还列出了printf所用的格式说明符。
printf使用的转义字符 |转义字符 |定义 | :-------- | --------:| :--: | |c| 字符 |s| 字符串 |d |十进制整数 |ld| 十进制长整数 |u| 十进制无符号整数 |lu| 十进制无符号长整数 |x| 十六进制整数 |lx| 十六进制长整数 |o| 八进制整数 |lo| 八进制长整数 |e |用科学记数法(e 记数法)表示的浮点数 |f| 浮点数 |g| 选用e或f中较短的一种形式
printf的修饰符 |字符| 定义| | :-------- | --------:| :--: | |- |左对齐修饰符 |# |显示8 进制整数时在前面加个0,显示16 进制整数时在前面加0x |+ |显示使用d 、e 、f 和g 转换的整数时,加上正负号+或- |0 |用0而不是空白符来填充所显示的值 printf的格式说明符 |格式说明符| 功能|示例|输出| | :-------- | --------:| :--: | |%c |打印单个ASCII字符 |printf("The character is %c\n",x)|The character is A |%d |打印一个十进制数 |printf("The boy is %d years old\n",y)|The boy is 15 years old |%e |打印数字的e 记数法形式 |printf("z is %e\n",z) |z is 2.3e+0 1 |%f |打印一个浮点数 |printf("z is %f\n", 2.3 * 2)|z is 4.600000 |%o |打印数字的八进制 |printf("y is %o\n",y) |z is 17 |%s |打印一个字符串 |print("The name of the culprit is %s\n",$1) |The name of the culprit is Bob Smith |%x |打印数字的十六进制值 |printf("y is %x\n",y) |x is f
打印变量时,输出所在的位置称为"域"(field),域的宽度(width)是指该域中所包含的字符个数。下面这些例子中, printf控制串里的管道符(竖杠)是文本的一部分, 用于指示格式的起始与结束。 范例
$ echo "Linux" | awk '{printf "|%-15s|\n",$1}'
|Linux |
说明:对于echo命令的输出,Linux是经管道发给awk。printf函数包含一个控制串。百分号让printf做好准备,它要打印一个占15个格、向左对齐的字符串,这个字符串夹在两个竖杠之间,并且以换行符结尾。百分号后的短划线表示左对齐。控制串后面跟了一个逗号和$1。printf将根据控制串中的格式说明来格式化字符串Linux。 范例
$ echo "Linux" | awk '{printf "|%15s|\n",$1}'
| Linux|
说明:字符串Linux被打印成一个占15 格、向右对齐的字符串,夹在两个竖杠之间,以 换行符结尾。 范例
$ cat employees
Tom Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500
$ awk '{printf "The name is: %-15s ID is %8d\n",$1,$3}' employees
The name is Tom ID is 4424
The name is Mary ID is 5346
The name is Sally ID is 1654
The name is Billy ID is 1683
说明:要打印的字符串放置在两个双引号之间。第一个格式说明符是%-15s,它对应的参数是$1,紧挨着控制串的右半边引号后面的那个逗号。百分号引出格式说明:短划线表示左对齐,15s表示占15格的字符串。这条命令用来打印一个左对齐、占15格的字符串,后面跟着字符串的ID和一个整数。
格式:%8d表示在字符串的这个位置打印$2 的十进制(整数)值。这个整数占8格,向右对齐。您也可以选择将加引号的字符串和表达式放在圆括号里。
7. 文件中的awk命令
如果awk命令被写在文件里,就要用-f选项指定awk的文件名,后面再加上所要处理的输入文件的文件名。awk从缓冲区读入一条记录,接着测试awk文件中的每一条命令,然后对读入的记录执行命令。处理完第一条记录后,awk将其丢弃,接着将下一条记录读入缓冲区,依次处理所有记录。如果没有模式限制,默认的操作就是打印全部记录。而模式如果没有相应的操作,则默认行为是打印匹配它的记录。 范例
$ cat employees
Tom Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500
$ cat awkfile
/^Mary/{print "Hello Mary!"}
{print $1, $2, $3}
$ awk -f awkfile employees
Tom Jones 4424
Hello Mary!
Mäty Adams 5346
Sally Chang 1654
Billy Black 1683
说明: 1.如果记录以正则表达式Mary开头,则打印字符串"Hello Mary!"。操作取决于它前面的模式是否匹配。且打印的字段之间以空白符分隔。 2.打印每条记录的第1 、第2、第3字段。awk对每行都执行该操作,因为没有限制该操作的模式。
8. awk记录
在awk命令看来输入数据具有格式和结构, 而不是无休止的字符串。默认情况下, 每一行称为一条记录,以换行符结束。
记录分隔符默认情况下,输入和输出记录的分隔符(行分隔符)都是回车符(换行符),分别保存在awk的内置变量ORS和RS中。ORS和RS的值可以修改,但是只能以特定方式进行修改。
变量$0:awk用$0指代整条记录(当$0因替换或赋值而改变时,NF的值,即字段的数目值也可能改变)。换行符的值保存在awk的内置变量RS中,其默认值为回车。
9. awk字段
每条awk记录都是由字段(field)组成,默认情况下,字段间用空白符(即空格或制表符)分隔。每个这样的词称为一个字段,awk在内置变量NF中保存记录的字段数。NF的值因行而异,其上限与具体版本的实现相关,通常是每行最多100个字段。可以创建新的字段。下面这个例子中有4条记录(行)和5个字段(列)。每条记录都是从第一个字段(用$1表示)开始,然后是第二个字段($2) ,以此类推。
10. 字段分隔符
输入字段分隔符:awk的内置变量FS中保存了输入字段分隔符的值。使用FS的默认值时,awk用空格或制表符来分隔字段,并且删除各字段前多余的空格或制表符。可以通过在BEGIN语句中或命令行上赋值来改变FS的值。接下来我们就要在命令行上给FS指定一个新的值。在命令行上改变FS的值需要使用-F选项,后面指定代表新分隔符的字符。
从命令行改变字段分隔符:范例中演示了如何使用-F选项在命令行中改变输入字段分隔符。
$ cat employees
Tom Jones:4424:5/12/66:543354
Mary Adams:5346:11/4/63:28765
Sally Chang:1654:7/22/54:650000
Billy Black:1683:9/23/44:336500
$ awk -F: '/Tom Jones/{print $1,$2}' employees
Tom Jones 4424
说明:-F选项用来在命令行重新设置输入字段分隔符的值。当冒号紧跟在-F选项的后面时,awk 就会在文件中查找冒号,用以分隔字段。
使用多个字段分隔符:你可以指定多个输入字段分隔符。如果有多个字符被用于字段分隔符FS,则FS对应是一个正则表达式字符串,并且被括在方括号中。下面的范例中,字段分隔符是空格、冒号或制表符。
$ awk -F'[ :\t]' '{print $1,$2,$3}' employees
Tom Jones 4424
Mary Adams 5346
Sally Chang 1654
Billy Black 1683
说明:-F选项后面跟了一个位于方括号中的正则表达式,当遇到空格、冒号或制表符时,awk会把它当成字段分隔符。这个表达式两头加了引号,这样就不会被shell当成自己的元字符来解释(注意, shell使用方括号来进行文件名扩展)。
输出字段分隔符:默认的输出字段分隔符是单个空格,被保存于awk的内置变量OFS中。此前的所有例子中,我们都是用print语句把输出打印到屏幕上。因此,无论OFS如何设置,print语句中用于分隔字段的逗号,在输出时都被转换成OFS的值。如果用OFS的默认值,则$1和$2之间的逗号会被转换为单个空格,print函数打印这两个字段时会在它们之间加一个空格。
如果没有用逗号来分隔字段,则输出结果中的字段将堆在一起。另外,OFS的值可以改变。
范例
$ cat employees
Tom Jones:4424:5/12/66:543354
Mary Adams:5346:11/4/63:28765
Sally Chang:1654:7/22/54:650000
Billy Black:1683:9/23/44:336500
$ awk -F: '/Tom Jones/{print $1,$2,$3,$4}' employees
Tom Jones 4424 5/12/66 543354
说明:输出字段分隔符,即空格,保存在awk的变量OFS中。字段间的逗号在输出时被转换成OFS中的值。字段被打印到标准输出上,字段之间用空格分隔。
范例
$ awk -F: '/Tom Jones/{print $0}' employees
Tom Jones:4424:5/12/66:543354
说明:变量$0按输入文件中的原样保存当前记录。记录被原封不动地显示到屏幕上。
$ awk -F: '/Tom Jones/{OFS="|";print $1,$2,$3,$4}' employees
Tom Jones|4424|5/12/66|543354
说明:OFS的值可以修改,输出时,“,”替换为修改后的OFS的值。
11. awk模
awk模式用来控制awk命令对输入的文本行执行什么操作。模式由正则表达式、判别条件真伪的表达式或二者的组合构成。awk的默认操作是打印所有使表达式结果为真的文本行。模式表达式中暗含着if语句。如果模式表达式含有if(如果)的意思,就不必用花括号把它括起来。当if是显式地给出时,这个表达式就成了操作语句,语法将不一样(参见条件语句 )。
范例
$ cat employees
Tom Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500
$ awk '/Tom/' employees
Tom Jones 4424 5/12/66 543354
说明:如果在输入文件中匹配到模式Tom,则打印Tom所在的记录。如果没有显式地指定操作,默认操作是打印文本行,等价于命令:
$ awk '$0~/Tom/{print $0}' employees
范例
$ awk '$3 < 4000' employees
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500
说明:如果第3个字段的值小于4000,则打印该记录。
12. awk操作
awk操作(action)是花括号中以分号分隔的语句。如果操作前面有个模式,则该模式控制执行操作的时间。操作可以是简单的语句或复杂的语句。同一行内的多条语句由分号分隔,独占一行的语句则以换行符分隔。 格式:
{action}
范例:
{print $1,$2}
说明:该操作用来打印前两个字段。 模式可以与操作结合使用。记住,操作是括在花括号中的语句,模式控制它后面第一个左花括号到第一个右花括号之间的操作。 格式:
模式 { 操作语句;操作语句; ...;}
或
模式{
操作语句
操作语句
}
范例
$ awk '/Tom/{print "Hello there, "$1}' employees
Hello there, Tom
说明:如果记录中包含模式Tom,就会打印出字符串"Hel1o there, Tom"。 如果没有为模式指定操作,就会打印所有匹配该模式的文本行。用于匹配字符串的模式包括了夹在两个正斜杠之间的正则表达式。
13. awk正则表达式
对awk命令而言,正则表达式是置于两个正斜杠之间、由字符组成的模式。 awk支持使用(与egrep相同的)正则表达式元字符对正则表达式进行某种方式的修改。如果输入行中的某个字符串与正则表达式相匹配,则最终条件为真,于是执行与该表达式关联的所有操作。如果没有指定操作,则打印与正则表达式匹配的记录。
范例
$ cat employees
Tom Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500
$ awk '/Mary/' employees
Mary Adams 5346 11/4/63 28765
说明:显示文件employees中所有包含正则表达式Mary的行。
范例
$ awk '/Mary/{print $1,$2}' employees
Mary Adams
说明:显示文件employees中所有包含正则表达式Mary的行的头两个字段。
awk正则表达式元字符 |元字符 |说明 | :-------- | --------:| :--: | |^ |匹配串首 |$| 匹配串尾 |.| 匹配单个任意字符 |* |匹配零个或多个前导字符 |+ |匹配一个或多个前导字符 |?| 匹配零个或一个前导字符 |[ABC] |匹配指定字符组(即A、B和C)中任一字符 |[^ABC]| 匹配任何一个不在指定字符组(即A、B和C)中的字符 |[A-Z]| 匹配A至Z之间的任一字符 |A|B| 匹配A或B |(AB)+| 匹配一个或多个AB 的组合,例如: AB 、ABAB 、ABABAB |* |匹配星号本身 |& |用在替代串中,代表查找串中匹配到的内容
以下是grep与sed支持,而awk不支持的元字符。 awk不支持的元字符 |元字符 |说明 | :-------- | --------:| :--: | |\< \>| 单词定位 |\( \)| 向前引用 |\{ \} |重复
14. 匹配整行
如果没有指定操作,则单个正则表达式将对整行进行模式匹配,并打印出所匹配的行。可以使用元字符^来表示需要进行行首匹配的正则表达式。
范例
$ cat employees
Tom Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500
$ awk '/^Mary/' employees
Mary Adams 5346 11/4/63 28765
说明:显示文件employees中所有以正则表达式Mary开头的行。
范例
$ awk '/^[A-Z][a-z]+ /' employees
Tom Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500
说明:显示文件employees中所有以大写字母开头、后跟一个或多个小写字母、再跟一个空格的行。
15. awk匹配操作符
匹配操作符(~)用于对记录或字段的表达式进行匹配。
范例
$ cat employees
Tom Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500
$ awk '$1 ~ /[Bb]ill/' employees
Billy Black 1683 9/23/44 336500
说明:显示所有在第一个字段里匹配到Bill或bill的行。
范例
$ awk '$1 !~ /ly$/' employees
Tom Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765
说明:显示所有第一个字段不是以ly结尾的行。
POSIX字符类 POSIX(the Portable Operating System Interface,可移植操作系统接口)是一种工业标准,确保程序可以跨操作系统移植。为了保证可移植, POSIX可以识别字符、阿拉伯数字和符号在不同国家或不同场合的编码方法,以及时间和时期的不同表示。为了处理不同类型的字符,POSIX增加了基本的和扩展的正则表达式,下表对括号字符类进行了说明。对于UNIX,gawk支持这些新的元字符类,而awk不支持;而对于Linux,则应当明白awk是链接到gawk上的,也就是说awk和gawk的命令同样有效。
POSIX增加的括号字符类 |括号类| 含义 | :-------- | --------:| :--: | |[:alnum:] |字母数字字符 |[:alpha:] |字母字符 |[:cntrl:] |控制字符 |[:digit:] |数字字符 |[:graph:] |非空白字符(非空格、控制字符等) |[:lower:] |小写字母 |[:print:]| 与[:graph:]相似,但是包含空格字符 |[:punct:] |标点字符 |[:space:]| 所有的空白字符(换行符、空格、制表符) |[:upper:] |大写字母 |[:xdigit:] |允许十六进制的数字(0-9a-fA-F)
在类中, [:alnum:]是另一种表示A-Z、a-z和0-9的形式,使用这种类时,必须要用另外一个方括号扩起来,例如"A-Za-z0-9" 自己本身不是正则表达式,而[A-Za-z0-9]则是正则表达式。类似地,[:alnum:] 应该写为[[:alnum:]] 。第一种形式[A-Za-z0-9]与方括号形式[[:alnum:]] 的不同之处在于,前者依赖于ASCII字符编码的形式,而第二种形式允许其他语言的字符在类中表示,例如瑞典语和德语。
$ gawk '/[[:lower:]]+g[[:space:]]+[[:digit:]]/' employees
Sally Chang 1654 7/22/54 650000
说明:gawk搜索一个或多个小写字母,后面跟着一个字母"g",再后面为一个或多个空格,然后是一个数字的模式。
16. awk关系运算符
下表列出了所有关系运算符。关系表达式的计算结果为真时,表达式的值为1;反之,则为0。 关系运算符 |运算符 |含义 |示例 | :-------- | --------:| :--: | |< |小于 |x < y |<= |小于等于 |x <= y |== |等于 |x == y |!= |不等于 |x != y |>= |大于等于 |x >= y |> |大于 |x > y |~ |与正则表达式匹配| x ~ /y/ |!~ |与正则表达式不匹配| x !~ /y/ 范例
$ cat employees
Tom Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500
$ awk '$3==5346' employees
Mary Adams 5346 11/4/63 28765
说明:如果某行的第3个字段的值等于5346,则条件为真,awk将执行默认操作一一打印该行。如果表达式中隐含着if条件,它就是一个条件模式测试。
$ awk '$3 > 5000{print $1}' employees
Mary
说明:如果某条记录的第3个字段的值大于5000,awk就打印该记录的第1个字段。
$ awk '$2 ~ /Adam/' employees
Mary Adams 5346 11/4/63 28765
说明:如果记录的第2个字段匹配正则表达式Adam,则打印该记录。
$ awk '$2 !~ /Adam/' employees
Tom Jones 4424 5/12/66 543354
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500
说明:如果记录的第2个字段不匹配正则表达式Adam,则打印该记录。假设表达式的值是一个数,与其比较的却是个字符串值,二者之间的运算符如果是用于数值比较的运算符,则字符串值将被转换为一个数值,如果是用于字符串比较的运算符,则数值被转换为字符串值。
17. 条件表达式
数值常量可以表示为整数(如243)、浮点数(如3.14)或用科学记数法表示的数(如.723E-1或3.4e7)。字符串则括在双引号中,例如"Hello world"。
初始化与强制类型转换:只要在awk程序中被提到,变量就开始存在。变量可以是一个字符串或一个数字,也可以既是字符串又是数字。变量被设置后,就变成与等号右边那个表达式相同的类型。未经初始化的变量的值是0就"",究竟是哪个则取决于它们被使用时的上下文。
name = "Nancy" name是字符串
x++ x是数字,它被初始化为0,然后加1
number = 35 number是数字
如果要将一个字符串强制转换为数字,方法为:
name + 0
将数字转换成字符串的方法则是:
number""
所有由split函数创建的字段或数组元素都被视为字符串,除非它们只包含数字值。如果某个字段或数组元素为空,它的值就是空串。空行也可以被视为空串。
18. 算术运算
可以在模式中执行计算。awk命令都将按浮点方式执行算术运算。下表列出了所有的算术运算符。 |运算符| 含义| 例子 | :-------- | --------:| :--: | |+| 加| x+y |-| 减| x-y |*| 乘| x*y |/ 除 |x/y |% |模 |x%y |^| 幂| x^y 范例
$ awk '$3*$4==500' filename
说明:awk将记录的第3个字段($3)与第4个字段($4)的值相乘,如果乘积等于500,则显示该行(假定filename是含有输入数据的文件)。
19. 逻辑操作符和复合模式
逻辑操作符用来测试表达式或者模式的真假。符号&&,表示逻辑与,当所有表达式均为真时,整个表达式为真,只要有一个表达式为假,整个表达式就为假。符号||表示逻辑或,只要有一个表达式或者模式为真,整个表达式就为真。如果所有表达式为假,则整个表达式为假。
复合模式是用逻辑运算符(参见下表)将模式组合起来形成的表达式。表达式的计算是从左往右的。 逻辑运算符 |运算符| 含义| 例子 | :-------- | --------:| :--: | |&& |逻辑与| a && b |||| 逻辑或| a || b |!| 逻辑非| !a 范例
$ awk '$2 > 5 && $2 <= 15' filename
说明:awk将显示同时符合这两个条件的行,即该行的第2个字段($2)的值大于5,且小于或等于15。运算符&&要求两个条件都必须为真(假定filename 是包含输入数据的文件)。 范例
$ awk '$3 == 100 || $4 > 50' filename
说明:awk将显示符合两个条件之一的行,即第3个字段等于100或第4个字段大于50的行。运算符||只要求有一个条件必须为真(假定filename是包含输入数据的文件)。 范例
$ awk '!($2 < 100 && $3 < 20)' filename
说明:如果两个条件都为真,awk将否定该表达式并取消对该行的打印。所以显示出来的行都有一个条件为假,甚至两个条件都为假。一元运算符!对条件的结果求反,所以,如果表达式本来产生一个为真的条件,“非”操作会将其变为假,反之亦然(假定filename 是包含输入数据的文件)。
20. awk范围模式
awk范围模式先匹配从第一个模式的首次出现到第二个模式的首次出现之间的内容,然后匹配从第一个模式的下一次出现到第二个的下一次出现,以此类推。如果匹配到第一个模式而没有发现第二个模式, awk 就将显示从第一个模式首次出现的行到文件末尾之间的所有行。 范例
$ awk '/Tom/,/Suzanne/' filename
说明:awk将显示从Tom首次出现的行到Suzanne首次出现的行这个范围内的所有行,包括两个边界在内。如果没有找到Suzanne,awk将继续打印各行直至文件末尾。如果打印完Tom到Suzanne的内容之后,又出现了Tom, awk就又一次开始显示行,直至找到下一个Suzanne 或文件末尾。
21. awk变量
数值常量可以表示为整数(如243)、浮点数(如3.14)或用科学记数法表示的数(如.723E-1或3.4e7)。字符串则括在双引号中,例如"Hello world"。
初始化与强制类型转换:只要在awk程序中被提到,变量就开始存在。变量可以是一个字符串或一个数字,也可以既是字符串又是数字。变量被设置后,就变成与等号右边那个表达式相同的类型。未经初始化的变量的值是0就"",究竟是哪个则取决于它们被使用时的上下文。
name = "Nancy" name是字符串
x++ x是数字,它被初始化为0,然后加1
number = 35 number是数字
如果要将一个字符串强制转换为数字,方法为:
name + 0
将数字转换成字符串的方法则是:
number""
所有由split函数创建的字段或数组元素都被视为字符串,除非它们只包含数字值。如果某个字段或数组元素为空,它的值就是空串。空行也可以被视为空串。
22. 用户自定义变量
用户自定义变量的变量名可以由字母、数字和下划线组成,但是不能以数字开头。awk的变量不用声明其类型,awk可以从变量在表达式中的上下文推导出它的数据类型。如果变量未被初始化,awk会将字符串变量初始化为空串,将数值变量初始化为0。必要时,awk会将字符型变量转换为数值型变量,或者反向转换。对变量赋值要使用awk的赋值运算符(参见下表)。 赋值运算符 |运算符| 含义| 等效表达式| | :-------- | --------:| :--: | |= |a=5| a=5| |+= |a=a+5 |a+=5| |-= |a=a-5 |a-=5| |*= |a=a*5 |a*=5| |/=| a=a/5| a/=5| |%=| a=a%5| a%=5| |^= |a=a^5| a^=5| 最简单的赋值方式是求出表达式的结果,然后将其赋给变量。
范例
$ awk '$1 ~ /Tom/{wage = $2 * $3;print wage}' filename
说明:awk将在第1个字段中扫描Tom。如果发现某一行符合条件,就将其第2个字段的值与第3个字段的值相乘,乘积赋值给用户定义的变量wage。由于乘法是算术运算,所以awk把wage的初始值设为0。
递增和递减运算符:如果要将操作数加1,可以使用递增运算符。表达式x++等价于x = x + 1。 类似地,递减运算符则使操作数减少1 。表达式x--等价于x=x-1 。当进行循环操作时,如果只需要递增或递减一个计数器,这种运算符就很有用。递增和递减运算符可以放在操作数的前面,如++x; 也可以置于操作数之后,如x++。用于赋值语句时,这两个运算符的位置不同可能会造成运算结果的差异。
{x = 1;y = x++;print x,y}
上面这个例子中的++称为后递增运算符: y先被赋值为1,然后x才加1。这样,当所有运算做完后,y等于1 ,而x等于2。
{x = 1;y = ++x;print x,y}
上面这个例子中的++称为先递增运算符:先将x加1,然后才将值2赋给y。这样,在所有运算完成后,y等于2,x也等于2。
命令行上的用户自定义变量:可以在命令行上对变量赋值,然后将其传递给awk脚本。如果想对参数处理和ARGV有更多了解,请青参见“处理命令行参数”
awk -F: -f awkscript month=4 year=2015 filename
说明:用户自定义的变量month和year分别被赋值为4和2015。在awk脚本中可以使用这些变量,就好像它们是在脚本中生成的一样。注意,如果命令行中filename的位置在变量之前,这些变量将不能在BEGIN语句中使用(参见“awk的BEGIN与END模式”)。
字段变量:字段变量可以像用户自定义的变量一样使用,唯一的区别是它们引用了字段。新的字段可以通过赋值来创建。字段变量引用的字段如果没有值,则被赋值为空串。字段的值发生变化时, awk会以OFS的值作为字段分隔符重新计算$0变量的值。字段数目通常被限制在100 以内。
范例
$ awk '{$5 = 1000 * $3 / $2; print}' filename
说明:如果不存在第5个字段($5),awk将创建它并将表达式1000*$3/$2的结果赋给它。如果存在第5个字段,就直接将表达式的结果赋给它,覆盖该字段原来的内容。
范例:
$ awk '$4 == "CA" {$4 = "California";print}' filename
说明:如果第4个字段($4)匹配字符串CA,awk就将其重新赋值为California。双引号是必需的,如果没有这对双引号,字符串CA就会被当成一个初始值为空的用户自定义变量。
内量变量:内置变量的名字都是大写的。它们可以被用于表达式,也可以被重置。请参见下表中所列的内置变量。
内置变量 |变量名| 含义 | :-------- | --------:| :------: | |ARGC |命令行参数的数目 |ARGIND |命令行中当前文件在ARGV 内的索引 |ARGV |命令行参数构成的数组 |CONVFMT |数字转换格式,默认为%.6g |ENVIRON |包含当前shell 环境变量值的数组 |ERRNO |当使用getline 函数进行读操作或者使用close 函数时,因重定向操作而产生的系统错误描述 |FIELDWIDTHS |在分隔固定宽度的列表时,使用空白而不是FS 进行分隔的字段宽度列表 |FILENAME |当前输入文件的文件名 |FNR |当前文件的记录数 |FS |输入字段分隔符,默认为空格 |IGNORECASE |在正则表达式和字符串匹配中不区分大小写 |NF |当前记录中的字段数 |NR |目前的记录数 |OFMT| 数字的输出格式 |OFS| 输出字段分隔符 |ORS |输出记录分隔符 |RLENGTH |match 函数匹配到的字符串的长度 |RS |输入记录分隔符 |RSTART |match 函数匹配到的字符串的偏移量 |RT |记录终结符,对于匹配字符或者用RS 指定的regex,awk将RT设置到输入文本 |SUBSEP |数组下标分隔符
范例
$ cat employees
Tom Jones:4423:5/12/66:543354
Mary Adams:5346:11/4/63:28765
Sally Chang:1654:7/22/54:650000
Mary Black:1683:9/23/44:336500
$ awk -F: '$1 == "Mary Adams"{print NR,$1,$2,$NF}' employees
2 Mary Adams 5346 28765
说明:-F选项把字段分隔符设置为冒号。print函数依次打印出记录号、第1个字段、第2个字段和最后一个字段($NF)。
范例
$ awk -F: '{IGNORECASE=1};\
$1 == "mary adams"{print NR,$1,$2,$NF}' employees
2 Mary Adams 5346 28765
说明:-F选项把字段分隔符设置为冒号。若awk的内置变量IGNORECASE为非0值,则在正则表达式和字符串匹配中不区分大小写。接着,字符串mary adams匹配(写成Mary Adams也没关系)。最后,print函数依次打印出记录号、第1个字段、第2个字段和最后一个字段($NF) 。
23. awk的BEGIN与END模式
BEGIN模式后面跟了一个操作块。awk命令必须在对输入文件进行任何处理之前先执行该操作块。实际上,不需要任何输入文件,也能对BEGIN块进行测试,因为awk要在执行完BEGIN操作块后才开始读取输入。BEGIN操作常常被用于修改内置变量(OFS、RS、FS等)的值、为用户自定义变量赋初值和打印输出的页眉或标题。
范例
$ awk 'BEGIN{FS=":";OFS="\t";ORS="\n\n"}{print $1,$2,$3}' file
说明:在处理输入文件之前,awk先把字段分隔符(FS)设为冒号,把输出分隔符(OFS)设为制表符,还把输出记录分隔符(ORS)设为两个换行符。如果BEGIN的操作块中有两条或两条以上语句,必须用分号分隔它们或每行只写一条语句(在shell的命令提示符下输入时,必须用反斜杠来转义换行符)。
范例
$ awk 'BEGIN{print "MAKE YEAR"}'
MAKE YEAR
说明:awk将显示MAKE YEAR。awk打开输入文件之前先执行该print函数,即使没有指定输入文件,awk也照样打印MAKE和YEAR。调试awk脚本时,可以先测试好BEGIN块的操作,再编写程序的其余部分。
END模式不匹配任何输入行,而是执行任何与之关联的操作。awk处理完所有输入行之后才处理END模式。
范例
$ awk 'END{print "The number of record is " NR}' filename
The number of record is 5
说明:awk处理完整个文件后才开始执行END块。此时NR的值是最后这条记录的记录号。
范例
$ awk '/Mary/{count++}END{print "Mary was found " count " times."}' employees
Mary was found 2 times.
说明:每遇到一个包含模式Mary的行,用户自定义的变量counter的值就加1。awk处理完整个文件后,END块打印字符串Mary was found,再跟上变量count的值和字符串times。
24. 输出重定向
将awk命令的输出重定向到UNIX/Linux 文件时,会用到shell的重定向操作符。重定向的目标文件名必须用双引号括起来。**如果使用的重定向操作符为>,则文件被打开并请空。**文件一旦打开,就会保持打开状态直至显示关闭或awk程序终止。此后print语句的输出都将追加到文件尾部。
符号>>也用于打开文件,但是不消除文件内容,它只向文件追加内容。
范例
$ awk '$4 >= 70 {print $1,$2 > "passing_file"}' filename
说明:如果记录的第4个字段的值大于或等于70,它的头两个字段就被打印到文件 passing_ file 中。
25. 输入重定向(getline)
getline函数: getline函数用于从标准输入、管道或文件(非当前处理的文件)读取输入。getline函数用于读取下一输入行,并且设置内置变量NF、NR和FNR。如果读到一条记录,函数就返回1. 如果读到EOF(end of fiJe,文件末尾)则返回0。如果发生错误,比如打开文件失败,则getline函数返回-1。
范例
$ awk 'BEGIN{"date" | getline d;print d}' filename
Thu Jan 14 11:24:24 PST 2015
说明:先执行UNIX/Linux的date命令,将输出通过管道发给getline,再通过getline将传来的内容赋值给用户自定义的变量d,然后打印d。 范例
$ awk 'BEGIN{"date" | getline d;split(d,mon);print mon[2]}' filename
Jan
说明:先执行date命令,将输出通过管道发给getline,接着,getline从管道读取输入,然后保存在用户自定义变量d中。split函数从d中生成一个名为mon的数组.最后,程序打印出数组mon的第2个元素。
范例
$ awk 'BEGIN{while("ls" | getline) print}'
a.out
db
dbook
file
说明:ls命令的输出将传递给getline: 每循环一次,getline就从ls的输出中读取一行,并将其显示到屏幕上,不需要输入文件, 因为awk会在文件打开之前先处理完BEGIN块。
范例
$ awk 'BEGIN{printf "What is your name?";\
getline name < "/dev/tty"}\
$1 ~ name {print "Found " name " on line ", NR "."}\
END{print "See ya, " name "."}' filename
What is your name? Ellie < Waits for input from user >
Found Ellie on line 5.
See ya, Ellie.
说明: 1.在屏幕上显示What is your name ? 然后等待用户响应. get1ine函数将从终端(/dev/tty)接收输入,直到用户换行,然后,将输入保存在用户自定义的变量name中。 2.如果第一个字段匹配之前赋给name的值,则执行print函数。 3.END语句打印出"See ya,",然后显示Ellie(保存在变量name中的值),再跟上一个句点。
范例
$ awk 'BEGIN{while (getline < "/etc/passwd" >0) lc++;print lc}' file
16
说明:awk将逐行读取文件/etc/passwd, lc随之递增直至到达EOF,然后打印lc的值,即文件passwd的行数。注意,如果文件不存在,get1ine的值将是-1。如果读到文件尾,返回值是0,而读到一行时,返回值则是1。因此,命令
while (getline < "/etc/junk")
遇到文件/etc/junk不存在的情况时,会进入死循环,因为返回值-1导致条件为真。
26. awk管道
如果在awk命令中打开了管道,就必须先关闭它才能打开另一个管道。管道符右边的命令被括在双引号之间。每次只能打开一个管道。
范例
$ cat names
john smith
alice cheba
george goldberg
susan goldberg
tony tram
barbara nguyen
elizabeth lone
dan savage
eliza goldberg
john goldenrod
$awk '{print $1,$2 | "sort -r +1 -2 +0 -1"}' names
tony tram
john smith
dan savage
barbara nguyen
elizabeth lone
john goldenrod
susan goldberg
george goldberg
eliza goldberg
alice cheba
说明:awk使用管道将print语句的输出结果发给Linux的sort命令作为输入。sort命令将以第2个字段为主键、第1个字段为次键对输入进行逆排序。这种情况下,Linux命令必须被双引号括起来。
关闭文件和管道 如果打算再次在awk程序中使用某个文件或管道进行读写,则可能要先关闭程序,因为其中的管道会保持打开状态直至脚本运行结束。注意,管道一旦被打开,就会保持打开状态直至awk退出。因此,END块中的语句也会受管道的影响。下面这个例子中,END块的第一行命令将用来关闭管道。
范例:
awk '{print $1,$2 | "sort -r +1 -2 +0 -1"} END {close("sort -r +1 -2 +0 -1")}' filename
说明: 1.awk把输入文件的每一行记录都通过管道发给Linux的实用程序sort。 2.执行到END块时,管道被关闭。双引号中的字符串必须与最初打开管道的pipe命令字符串完全一致。
system函数 awk的内置函数system以Linux系统命令作为参数,执行该命令并且将命令的退出状态返回给awk程序。它很像C语言的一个标准库函数,该函数恰巧也为system()。注意,作为参数的Linux命令必须加双引号。
格式 system("Linux Command")
范例
awk {system("cat "$1);system("clear")} filename
说明: 1.system函数以Linux的cat命令和输入文件的第1个字段作为参数。cat命令把第1个字段的值,即一个文件名,作为参数。 2.system函数以Linux的clear命令作为参数。shell将执行clear命令,清空屏幕。
27. awk if语句
if 语句 awk命令以if结构开头的话句属于操作语句。条件模式(conditional pattern) 中,if是隐含的。而条件操作语句的if则是直接声明的,后面跟了一个用圆括号括起来的表达式。如果该表达式的运算结果为真(非0或非空),则执行表达式后的语句(或语句块)。如果跟在条件表达式后面的语句不止一条,就要用分号或换行符把它们隔开,还要用花括号把这一组语句都括起来,以作为一个块来被执行。
格式
if(表达式) {
语句;语句;...
}
范例
$ awk '{if($6 > 50) print $1 "Too high"}' filename
说明:在if操作块中对表达式进行测试。如果第6个字段的值大于50,就执行打印语句。由于跟在表达式后面的是单条语句,所以不需要加花括号(filename代表输入文件)。
范例
$ awk '{if($6 > 20 && $6 <=50) {safe++;print "OK"}}' filename
说明:在if操作块中测试表达式。如果第6个字段的值大于20并且小于50,就要将表达式后面的那些语句作为一个块来执行,因此,必须用花括号把它们括起来。
if/else 语句 if/else语句实现双路判断。如果关键字if后面的表达式为真,就执行与该表达式关联的语句块。如果这个表达式的运算结果为假或0,则执行关键字else后面的语句块。如果if或else包含多条语旬,就必须用花括号把它们合成一个语句块。
格式
if(表达式) {
语句;语句;...
} else {
语句;语句;...
}
范例
$ awk '{if($6 > 50) {print $1 " Too high";} else {print "Range is OK";}}' filename
说明:如果第一个表达式为真,即第6个字段($6)的值大于50 ,则print函数打印第1个字段和字符串"Too high"。否则就执行else后的语句,打印字符串"Range is OK"。
范例
$ awk '{if($6 > 50) {count++;print $3;} else {x+5;print $2;}}' filename
说明:如果第一个表达式为真,即第6个字段($6)的值大于50,则执行表达式后面的这个语句块。否则就执行else后面的那个语句块。注意,语句块必须括在花括号中。
if/else和else if语句
if/else和else if语句提供了多路判断功能。如果跟在关键字if后的表达式为真,则执行与该表达式关联的语句块,同时,程序的控制流将跳到与最后一个else关联的最后一个右花括号后,从这个位置继续往下行。否则,控制转到else if测试与其关联的表达式。如果第一个else if的条件为真,则执行对应表达式后的语句。如果else if 的条件表达式都不为真,控制就转到else语句。这个else被称作默认操作,因为只要其他语句都不为真,就执行该else块。
格式
if(表达式) {
语句;语句;...
} else if(表达式) {
语句;语句;...
} else if(表达式) {
语句;语句;...
} else {
语句;语句;...
}
范例
$ awk {
if($3 > 89 && $3 <101) Agrade++
else if($3 > 79) Bgrade++
else if($3 > 69) Cgrade++
else if($3 > 59) Dgrade++
else Fgrade++
}END{
print "The number of failures is "Fgrade
} filename
说明
if语句是一个操作,因此必须用花括号括起来。表达式的计算是从左向右进行。如果第一个表达式为假,则整个表达式为假。如果第一个表达式为真,则计算符号逻辑与(&&)后面的那个表达式。如果整个表达式为真,则变量Agrade加1。
如果关键字if后面的表达式值为假,就测试这个else if的表达式。如果该表达式的值为真,就执行它后面的语句。也就是说,如果第3个字段($3)的值大于79,则变量Bgrade加1。
如果头两个条件语句都为假,就测试这个else if 表达式,如果第3个字段($3)的值大于69,则将变量Cgrade加1。
如果头三个条件语句都为假,就测试这个else if表达式,如果第3个字段($3)的值大于59,则将变量Dgrade 加1。
如果上面的表达式都不为真,就执行else块,将变量Fgrade加1。接下来的花括号将结束整个操作块。
28. awk循环
循环的功能是:当测试表达式的条件为真时,重复执行表达式后面的语句。循环常常被用来对记录中的每个字段重复执行某种操作,或者在END块中用来循环处理某个数组中的所有元素。awk有3种类型的循环:while 循环、for循环和特殊for循环,特殊for循环将在稍后介绍awk数组时讨论。
while循环 使用while循环的第一步是给一个变量设初值,然后在while表达式中测试该变量。如果求得表达式的值为真(非0),则进入循环体执行其中的语句。如果循环体内有多条语句,就必须用花括号把这些语句括起来。循环块结束之前,一定要更新用来控制循环表达式的变量,否则循环将无休止地进行下去。下面这个例子中,每处理一条新记录,循环控制变量就会被重置一次。
awk 'BEGIN{while(anum <= 20){anum++;print anum}}'
do/while循环与while 循环很相似,唯一的区别在于do/while要先执行循环体至少一次,然后才测试表达式。
范例
$ awk '{i=1; while(i<=NF){print NF,$i; i++ }}'filename
说明 变量i被初始化为1 ;当i小于或等于记录的字段数(NF)时,先执行print语句,然后将i加1。接下来又重新测试表达式,直至i大于NF的值。变量i要在awk开始处理下一条记录时被重置。
for循环 for循环和while循环基本相同,只不过for循环的圆括号中需要3个表达式,前两个分别是初始化表达式和测试表达式,第3个则用于更新测试表达式所用的变量。在awk的for循环中,圆括号里的第一条语句只能初始化一个变量(C语言中与之对应的语句则可以用逗号分隔的形式初始化多个变量)。
范例
$ awk '{for(i=1;i<=NF;i++) print NF,$i}' filename
说明:变量i被初始化为1,然后测试它是否小于或等于记录的字段数目(NF)。若是,print函数便打印出NF 和$i的值($i代表第i个字段),然后将i加1 (for循环经常会在END操作中与数组一同使用,循环处理数组的所有元素)。
29. awk控制语句
break和continue语句可以在某个特定条件为真时,使用break语句跳出循环。continue 语句的作用则是在特定条件为真时,让循环跳过continue之后语句,将控制转回循环顶部,开始下一轮循环。
范例
$ awk '{
for(x = 3; x <= NF; x++){
if($x < 0){ print "Bottomed out!"; break}
/*break out of for loop*/
}
}' filename
说明:如果字段$x的值小于0,则break语句将控制跳转到循环体的右花括号后面的那条语句,即跳出循环。
$ awk '{
for(x = 3; x <= NF; x++){
if($x < 0){ print "Bottomed out!"; continue}
/*starts next iteration of the for loop*/
}
}' filename
说明:如果字段$x的值等于0,则continue语句使控制转回循环顶部并开始执行,将从for循环的第3个表达式x++开始。
next语句 next语句从输入文件中取出下一行输入,然后从awk脚本的顶部重新开始执行。
范例
awk '{
if($1 ~ /Peter/){next}
else {print}
}' filename
说明:如果某一行的第一个字段包含Peter,awk就跳过该行,从输入文件中读取下一行,然后从头开始执行脚本。
exit语句 exit语句用于终止awk程序。它只能中断对记录的处理,不能跳过END语句。如果exit语句的参数是一个0-255之间的值(exit 1),这个值就会被打印在命令行上,以表明程序是否执行成功,井且指出失败的类型。
范例
$ awk '{exit 1}' filename
$ echo $?
1
说明:退出状态为0表示成功,退出状态非0则表示失败(这是Linux的统一约定)。退出状态由程序员决定是否在程序中提供。在这个例子中,命令返回的退出状态值为1。
30. awk数组介绍
数组在awk命令中被称为关联数组(associative arrays) ,因为它的下标既可以是数字也可以是字符串。下标通常又被称作键(key),并且与对应的数组元素的值相关联。数组元素的键和值都存储在awk程序内部的一个表中,该表采用的是散列算法。正是由于使用了散列算法,所以数组元素不是顺序存储的,如果将数组的内容显示出来,元素的排列顺序也许跟想象中的不一样。
和变量一样,数组也是被用到时才被创建,而且,awk还能判定这个数组用于保存数字还是字符串。根据使用时的上下文环境,数组元素被初始化为数字。或空字符串。数组的大小不需要声明。awk数组可用于从记录中收集信息,也可用于统计总数、计算词数、记录模式出现次数等应用。
由于awk数组的内容比较多,我们依次从以下几个方面介绍给大家。
30.1 关联数组的下标
使用变量作为数组索引请参见范例
范例
$ cat employees
Tom Jones 4424 5/12/66 543354
Mary Adams 5346 11/4/63 28765
Sally Chang 1654 7/22/54 650000
Billy Black 1683 9/23/44 336500
$ awk '{name[x++]=$2}END{for(i=0; i<NR; i++){print i,name[i]}}' employees
0 Jones
1 Adams
2 Chang
3 Black
说明:数组name的下标是用户自定义的变量x。运算符++表明这是一个数值型的变量。awk将x初始化为0,并且每次使用x后将其加1(所用的是后递增运算符)。每条记录的第2个字段都将赋值给数组name中的相应元素。END块使用for循环来循环处理数组,将从下标0开始,依次打印数组元素的值。下标只是一个键,所以不必从0开始。下标可以从任意值开始,数字或字符串都可以。 范例
$ awk'{id[NR]=$3}END{for(x=1; x<=NR; x++){print id[x]}}' employees
4424
5346
1654
1683
说明:awk变量NR保存当前记录的记录号。本例用NR作为下标,把每条记录的第3个字段赋值给数组中的相应元素。最后,for循环对数组进行循环处理,打印出保存在数组中的值。
30.2 特殊for循环
当下标为字符串或非连续的数字时,不能用for循环来遍历数组。这时候就要使用特殊for循环。特殊for循环把下标作为键来查找与之关联的值。 格式
$ awk '{
for(item in arrayname){
print arrayname[item]
}
}'
范例
$ cat db
1 Tom Jones
2 Mary Adams
3 Sally Chang
4 Billy B1ack
5 Tom Savaqe
6 Tom Chung
7 Reqqie Steel
8 Tommy Tucker
$ awk '/^Tom/{name[NR]=$1}END{for(i=1;i<=NR;i++)print name[i]}' db
Tom
Tom
Tom
Tommy
说明:如果在输入行的行首匹配到正则表达式Tom,就为数组name赋一个值。NR值(当前记录号),将作为name数组的索引。在每一行上匹配到Tom时,name数组就赋一个第一个字段($1)的值,当到达END块时,name数组仅包含name[l],name[5],name[6],name[8]这4个元素。因此,当使用for循环打印name数组的值时,索引2、3 、4、7为空。
范例
$ awk '/^Tom/{name[NR]=$1}END{for(i in name){print name[i]}}' db
Tom
Tommy
Tom
Tom
说明:用特殊for循环遍历数组,只打印有相应下标的元素的值。打印结果的次序是随机的,因为关联数组是以散列方式存储的。
30.3 用字符串作为数组下标
数组下标可以由包含单个字符或字符串的变量组成,如果是字符串,则必须用双引号引起来。 范例
$ cat datafile3
tom
mary
sean
tom
mary
mary
bob
mary
alex
$ cat awk.sc
/tom/{count["tom"]++}
/mary/{count["mary"]++}
END{print "There are " count["tom"] " Toms in the file and
"count["mary"]" Marys in the file."}
$ awk -f awk.sc datafile3
There are 2 Toms in the file and 4 Marys in the file.
说明: 1.数组count包含两个元素: count["tom"]和count["mary"]。这两个数组元素的初值都是0。每次匹配到tom时,数组元素count["tom"]的值都加1 。 2.同样的过程被应用于count["mary"]。注意,每行只会算一次,即使tom(或mary)在该行中出现多次。 3.END模式打印出每个数组元素的值。
30.4 使用字段的值作为数组下标
对于awk命令,任何表达式都可以用作数组的下标。所以,也可以用字段作下标。下面的例子中的程序用于计算所有名字在第2 个字段出现的次数,并引入了一种for循环的新形式。
for(index_value in array) statement
在前面介绍的例子中,END块中出现的for循环的工作过程如下:变量name被设为count数组的索引值,在每次for循环的迭代中,执行print操作,首先打印的是索引值,然后是保存在元素中的值(打印输出的次序无法确定)。
范例
$ cat datafile4
4234 Tom 43
4567 Arch 45
2008 Eliza 65
4571 Tom 22
3298 Eliza 21
4622 Tom 53
2345 Mary 24
$ awk '{count[$2]++}END{for(name in count)print name,count[name]}'
Tom 3
Arch 1
Eliza 2
Mary 1
说明:这条awk语句首先用记录的第2个字段作为数组count的下标。数组的下标随第2个字段的变化而变化,所以数组count 的第一个下标是Tom。而count["Tom"]中保存的值是1。然后,count["Arch"]、count["Eliza"]和count["Mary"]相继被设为10当在第2个字段中再次出现Tom时,count["Tom"]的值将被加1,于是它目前的值是2。Arch、Eliza和Mary再次出现时其过程类似。
范例
$ awk '{dup[$2]++; if(dup[$2] > 1){name[$2]++}}\
END{print "The duplicates were"\
for(i in name){print i,name[i]}}' datafile4
Tom 2
Eliza 2
说明:数组dup的下标是第2个字段的值,即人名。dup数组中元素的值最初都是0,每处理一条记录,相应元素的值就加1。如果名字重复出现,则对应该下标的元素值就会变成2,并相应地逐渐增加。如果dup数组中某个元素的值大于1,就会创建一个名为name的新数组,也是以第2个字段的值作为下标,用于记录出现次数大于1的人名。
30.5 split与delete函数
数组与split函数: awk的内置函数split能够将字符串拆分为词,然后保存在数组中。您可以指定字段分隔符,也可以就用FS的当前值。 格式
split(字符串,数组,字段分隔符)
split(字符串,数组)
范例
$ awk 'BEGIN{split("3/15/2015",date,"/");\
print "The month is " date[1] " and the year is "date[3] "}' filename
The month is 3 and the year is 2015.
说明:将字符串3/15/2015保存到数组date中,用正斜杠作为字段分隔符。现在date[1]中是3,date[2]中是15,而date[3] 中则是2015。字段分隔符用第3个参数指定,如未指定,就以FS的值做字段分隔符。
delete函数:delete函数用于删除数组元素。 范例
$ awk '{line[x++]=$2}END{for(x in line) delete(line[x])}' filename
说明:赋给数组line的值是第2个字段的值。所存记录都处理完后,特殊for循环将遍历数组的所有元素,并由delete函数来删除它们。
30.6 多维数组
awk命令虽然没有宣称支持多维数组,却提供了定义多维数组的方法。awk定义多维数组的方法是把多个下标串成字符串,下标之间用内置变量SUBSEP的值分隔。变量SUBSEP的值是"\034",这是个不可打印的字符,极少被使用,因此不太可能被用作下标中的字符。表达式matrix[2,8]其实就是数组matrix[2 SUBSEP 8] ,转换后所得的结果为matrix["2\0348"] 。因此,下标成了关联数组中的唯一标识符。 范例
$ cat datafile
1 2 3 4 5
2 3 4 5 6
7 8 9 0 1
$ awk '{
nf=NF
for(x=1;x<NF;x++){
matrix[NR,x]=$x
}
}END{
for(x=1;x<NR;x++){
for(y=1;y<nf;y++){
printf "%d",matrix[x,y]
}
printf "\n"
}
}' datafile
(输出)
1 2 3 4 5
2 3 4 5 6
7 8 9 0 1
说明: 1.将NF的值(字段数)赋给变量nf(该程序假定每条记录都是由5个字段组成)。 2.进入for循环,依次把输入行每个字段的字段号保存到变量x中。 3.matrix是一个二维数组。每个字段的值将赋给下标为NR(当前记录的记录号)和x的数组元素。 4.END块中的两个for循环被用来遍历matrix数组,并打印数组中保存的值。这个例子只是用来说明如何使用多维数组。
30.7 处理命令行参数
30.7 处理命令行参数 ARGV awk可以从内置数组ARGV中得到命令行参数,其中包括命令awk。但所有传递给awk的选项都不在其中。ARGV数组的下标从0开始。 ARGC ARGC是一个包含命令行参数个数的内置变量。
范例
$ cat argvs
# Scriptname:argvs
BEGIN{
for(i=0; i<ARGC; i++){
printf("argv[%d] is %s\n",i,ARGV[i])
}
printf("The number of arguments, ARGC=%d\n",ARGC)
}
$ awk -f argvs datafile
argv[0] is awk
argv[1] is datafile
The number of arguments, ARGC=2
说明 for循环先将i设为0,然后测试它是否小于命令行参数的个数(ARGC) ,再用printf函数依次显示出每个参数。所有参数处理完之后,最后那条printf 语句用来输出参数的个数ARGC。这个例子说明awk并不把命令行选项视为参数。
范例
$ awk -f argvs datafile "Peter Pan" 12
argv[0] is awk
argv[1] is datafile
argv[2] is Peter Plan
argv[3] is 12
The number of arguments,ARGV=4
说明:和上个例子一样,打印出所有参数。nawk命令被当成第一个参数,而-f选项和脚本文件名(即argvs)则被排除在外。
范例
$ cat datafile5
Tom Jones:123:03/14/56
Peter Pan:456:06/22/58
Joe Blow:145:12/12/78
Santa Ana:234:02/03/66
Ariel Jones:987:11/12/66
$ cat arging.sc
# Scriptname:arging.sc
BEGIN{FS=":";name=ARGV[2]
print "ARGV[2] is "ARGV[2]
}
$1 ~ name {print $0}
$ awk -f arging.sc datafile5 "Peter Pan"
ARGV[2] is Peter Pan
Peter Pan:456:06/22/58
nawk: can't open Peter Pan
input record number 5, file Peter Pan
source 1ine number 2
说明: 1.在BEGIN块中,ARGV[2]的值,即Peter Pan,被赋给变量name。 2.Peter Pan被打印出来了,但是,处理完datafile并将其关闭后,awk试图把Peter Pan作为输入文件打开。awk把参数都作为输入文件。
范例
$ cat arging2.sc
BEGIN{FS=";";name=ARGV[2]
print "ARGV[2] is "ARGV[2]
delete ARGV[2]
}
$1 ~ name {print $0}
$ awk -f arging2.sc datafile "Peter Pan"
ARGV[2] is Peter Pan
Peter Pan:456:06/22/58
说明:awk把ARGV数组的元素作为输入文件。且awk用完一个参数就将它左移,接着处理下一个,直到ARGV数组变空。如果某个参数使用后立刻被删除,那么这个参数就不会被当作下一个输入文件来处理。
31. awk字符串函数
sub和gsub函数 sub函数用于在记录中查找能够匹配正则表达式的最长且最靠左的子串,然后用替换串取代找到的子串。如果指定了目标串,就在目标串中查找能够匹配正则表达式的最长且最靠左的子串,并将找到的子串替换为替换串。若未指定目标串,则在整个记录中查找。
sub函数格式
sub(正则表达式,替换串);
sub(正则表达式,替换串,目标串);
范例
$ awk '{sub(/Mac/,"MacIntosh");print}' filename;
$ awk '{sub(/Mac/,"MacIntosh",$1);print}' filename
说明: 1.在记录($0)中第一次匹配到正则表达式Mac时, Mac被替换为字符串MacIntosh。sub函数只对每行中出现的第一个匹配字符串进行替换(请参见用于替换多次匹配的gsub函数)。 2.在记录的第1个字段($1)中第一次匹配到正则表达式Mac时,Mac被替换为字符串MacIntosh。**sub函数只对目标串中出现的第一个匹配字符串进行替换。**gsub函数则对字符串中的正则表达式进行全局替换,即替换所有在记录($0)中出现的正则表达式。
gsub函数格式
gsub(正则表达式,替换串);
gsub(正则表达式,替换串,目标串);
范例
$ awk '{gsub(/CA/,"California");print}' filename
$ awk '{gsub(/[Tt]om/,"Thomas",$1);print}' filename
说明: 1.记录($0)中找到的每个正则表达式CA都被替换为California。 2.在第一个字段中找到的每个正则表达式Tom或tom都被替换为Thomas。
index函数 index函数返回子串在字符串中第一次出现的位置。偏移量从位置1开始计算。
格式
index(字符串,子串)
范例
$ awk '{print index("hollow","low")}' filename
4
说明:返回的数字是子串low在字符串hollow中第一次出现的位置,偏移量从1开始计算。
length函数 length函数返回字符串中字符的个数。如果未指定参数,则length函数返回记录中的字符个数。
格式
length(字符串)
length
范例
$ awk '{print length("hello")}' filename
5
length函数返回字符串hello的字符个数。
substr函数 substr函数返回从字符串指定位置开始的一个子串。如果指定了子串的长度,则返回字符串的相应部分。如果指定的长度超出了字符串的实际范围,则返回其实际内容。
格式
substr(字符串,起始位置)
substr(字符串,起始位置,子串长度)
范例
$ awk '{print substr("Santa Claus",7,6)}' filename
Claus
说明:在字符串"Santa Claus" 中,打印从位置7开始、长度为6个字符的子串。
match函数 match函数返回正则表达式在字符串中出现的位置,如果未出现,则返回0。match函数把内置变量RSTART设为子串在字符串中的起始位置, RLENGTH则设为子串的长度.这些变量可以被substr函数用来提取相应模式的子串。
格式
match(字符串,正则表达式)
范例
$ awk 'END{start=match("Good ole USA",/[A-Z]+$/);print start}' filename
10
说明:正则表达式/[A-Z]+$/的意思是查找在字符串尾部连续出现的大写字母.找到的子串USA是从字符串"Good ole USA"的第10个字符开始的。如果字符串未能匹配到正则表达式,则返回0。
范例
$ awk 'END{start=match("Good ole USA",/[A-Z]+$/);\
print RSTART,RLENGTH}' filename
10 3
$ awk 'BEGIN{line="Good ole USA"};\
END{match(line,/[A-Z]+$/);\
print substr(line,RSTART,RLENGTH)}' filename
USA
说明: 1.变量RSTART被match函数设置为匹配到的正则表达式在字符串中的起始位置。变量RLENGTH则被设为子串的长度。 2.substr函数在变量line中查找子串,把RSTART和RLENGTH的值(由match函数设置)作为子串的起始位置和长度。
split函数 split函数使用由第3个参数指定的字段分隔符,把字符取拆分成一个数组。如果没有提供第3个参数,awk将把FS 的当前值作为字段分隔符。
格式
split(字符串,数组,字段分隔符)
split(字符串,数组)
范例
$ awk 'BEGIN{split("12/25/2001",date,"/");print date[2]}' filename
$ 25
说明:split函数把字符串12/25/2001拆分为数组date。以正斜杠作为字段分隔符。数组date的下标从1开始。awk将打印数组date的第2个元素。
sprinf函数 sprintf函数返回一个指定格式的表达式。可以在sprintf函数中使用printf函数的格式规范。
格式
variable = sprintf("含有格式说明的字符串",表达式1 ,表达式2,...,表达式n)
范例
$ awk '{line=sprintf("%-15s %6.2f ",$1,$3);print line}' filename
说明:按照printf的规范设置第1个和第3个字段的格式(一个左对齐、长度为15的字符串和一个右对齐、长度为6个字符的浮点数)。结果被赋给用户自定义的变量line。请参见printf函数。
32. 内置算术函数
下表列出了awk的内置算术函数,表中的x和y是任意表达式。 算术函数 |函数名 |返回值 | :-------- | --------:| :------: | |atan2(x,y) |值域内y/x的反正切 |cos(x) |x的余弦,x为弧度 |exp(x) |x的e指数函数 |int(x) |x的整数部分,当x>0时,向下取整 |log(x) |x的自然对数(底数为e) |rand() |随机数r(0 |sin(x) |x的正弦,x为弧度 |sqrt(x) |x的平方根 |srand(x) |x是rand()的新种子
33. 用户自定义函数
脚本中凡是可以出现模式操作规则的位置都可以放置用户自定义的函数。
格式
函数名(参数,参数,参数, ...){
语句
return 表达式
(注: return语句和表达式都是可选项)
}
变量以参数值的方式传递,且仅在使用它的函数中局部有效。函数使用的只是变量的副本。数组则通过地址或引用被传递,因此,可以在函数中直接修改数组的元素。函数中的任何变量,只要不是从参数列表中传来的,就都被视为全局变量,也就是说,该变量对整个awk程序都是可见的,而且,如果它在函数中发生了改变,即在整个程序中发生了改变。在函数中提供局部变量的唯一途径就是将它加入参数列表中。这类参数通常放在参数列表的末端。当调用函数时,如果没有指定某个形参的值,该参数就会被初始化为空。return语句会把控制权交还给调用者,可能还会返回一个值。
范例
$ cat grades
44 55 66 22 77 99
100 22 77 99 33 66
55 66 100 99 88 45
$ cat sorter.sc
# Scriptname: sorter
# It sorts numbers in ascending order
function sort (scores , num_elements , tmp , i , j) {
# temp, i , and j will be local and private,
# with an initial value of null.
for( i = 2; i <= num_elements ; ++i ) {
for ( j = i; scores [j-1] > scores[j]; --j ) {
temp = scores[j]
scores[j] = scores[j-1]
scores[j-1] = temp
}
}
}
{for ( i = 1; i <= NF; i++){
grades[i]=$i
}
sort(grades, NF) #Two arguments are psssed
for ( j = 1; j <= NF; ++j )
printf("%d", grades[j])
printf ("\n")
}
$ awk -f sorter.sc grades
22 44 55 66 77 99
22 33 66 77 99 100
45 55 66 88 99 100
说明 1.定义名为sort的函数,函数定义可以出现在脚本的任意位置,除了那些作为参数传递的变量外,所有其他变量的域都是全局的。即如果在函数中发生了变化,也就在整个awk脚本中发生了变化。数组是通过引用进行传递的。圆括号中共有5个形参,其中数组scores将通过引用被传递,所以,如果在函数中修改了这个数组中任何一个元素,原来的数组也会被修改.变量num_elements是一个局部变量,是原变量的一个副本。变量temp、i和j则是函数的局部变量。 2.外层的for循环将遍历一个整数数组,前提是该数组中至少有两个整数可用于比较。 3.内层的for循环用当前这个整数与数组中前一个整数(scores[j-1])进行比较。如果前一个整数大于当前这个整数,就把当前这个数组元素的值赋给变量temp。然后把前一个元素的值赋给当前元素。 4.外层循环至此结束. 5.函数定义的末尾. 6.脚本的第一个操作块由此开始。for循环遍历当前记录的所有字段,生成一个整数数组。 7.调用sort函数,把由当前记录生成的整数数组和当前记录的字段数作为参数传给它。 8.sort函数结束后,程序控制由此开始。这个for循环用于打印完成排序的数组中的元素。
34. 固定字段
下面这个例子中,字段都是固定宽度的,但没有使用字段分隔符。substr函数可以用来创建字段。
范例
$ cat fixed
031291ax5633(408)987-0124
021589bg2435(415)866-1345
122490de1237(916)933-1234
010187ax3458(408)264-2546
092491bd9923(415)134-8900
112990bg4567(803)234-1456
070489qr3455(415)899-1426
$ awk '{printf substr($0,1,6)" ";printf substr($0,7,6)" ";\
print substr($0,13,length)}' fixed
031291 ax5633 (408)987-0124
021589 bg2435 (415)866-1345
122490 de1237 (916)933-1234
010187 ax3458 (408)264-2546
092491 bd9923 (415)134-8900
112990 bg4567 (803)234-1456
070489 qr3455 (415)899-1426
说明 第1个字段通过从整个记录中提取子串得到,子串从记录第一个字符开始、长度为6个字符。接下来,打印一个空格。第2个字段是通过在记录中提取从位置7开始、长度为6个字符的子串得到,后跟一个空格。最后一个字段则是通过在整个记录中提取从位置13开始、到由行的长度所确定的位置之间的子串获得(如果未指定参数,length函数返回当前行($0)的长度)。
空字段 如果用固定长度的字段来存储数据,就可能出现一些空字段。下面这个例子中,substr函数被用来保存字段,而不考虑它们是否包含数据。
范例
$ cat db
xxx xxx
xxx abc xxx
xxx a bbb
xxx xx
$ cat awkfix
# Preserving empty fields. Field width is fixed.
{
f[1]=substr($O, 1, 3)
f[2]=substr($O, 5, 3)
f[3]=substr($O, 9, 3)
line=sprintf ("%-4s%-4s%-4s\n",f[1],f[2],f[3])
print line
}
$ awk -f awkfix db
xxx xxx
xxx abc xxx
xxx a bbb
xxx xx
说明:
打印文件db的内容。这个文件中有一些空字段。
数组f的第1个元素被赋值为由位置1开始、长度为3的记录的子串。
数组f的第2个元素被赋值为由位置5开始、长度为3的记录的子串。
数组f的第3个元素被赋值为由位置9开始、长度为3的记录的子串。
用sprintf函数设置好数组元素的格式,然后将它们赋值给用户自定义的变量line。
打印line的值,可以看到结果中空字段依然被保留。
带$、逗号或其他字符的数字 下面这个例子中,价格字段中包含一个美元符号和逗号。脚本必须删掉这些字符,才能把价格加起来得出总的开销。可以通过gsub函数来完成这一任务。
范例
$ cat vendor
access tech:gp237221:220:vax789:20/20:11/01/90:$1,043.00
alisa systems:bp262292:280:macintosh:new updates:06/30/91:$456.00
alisa systems:gp262345:260:vax8700:alisa talk:02/03/91:$1,598.50
apple computer:zx342567:240:rnacs:e-mail:06/25190:$575.75
caci:gp262313:280:sparc station:networkll.5:05/12/91:$1,250.75
datalogics:bp13.2455:260:microvax2:pagestation maint:07/01/90:$1,200.00
dec:zx354612:220:microvax2:vms srns:07/20/90:$1,350.00
$ awk -F: '{gsub(/\$/,"");gsub(/,/,"");cost +=$7};\
END{print "The tota1 is $" cost}' vendor
$7474
说明:第一个gsub函数用空字符串对美元符号(''$)进行全局替换;第二个gsub函数则用空串替换全部逗号。然后,将用户自定义变量cost与每行的第7个字段相加,再把每次的结果赋回给cost,由此统计出总数。END块打印出字符串"The total is $",后面跟着cost的值。
35. 多行记录
到目前为止,本教程用作例子的所有数据文件中,每条记录都自成一行。而在下面这个名为checkbook的示例数据文件中,记录之间用空行分隔,同一记录的字段之间则用换行符分隔。要处理这个文件,就必须将记录分隔符(RS)设为空值,而把字段分隔符(FS)设为换行符。
$ cat checkbook
1/1/04
#125
-695.00
Mortgage
1/1/04
#126
-56.89
PG&E
1/2/04
#127
-89.99
Safeway
1/3/04
+750.00
Paycheck
1/4/04
#128
-60.00
Visa
$ cat awkchecker
BEGIN{RS=""; FS="\n"; ORS="\n\n"}
{print NR,$1,$2,$3,$4}
$ awk -f awkchecker checkbook
1 1/1/04 #125 -695.00 Mortgage
2 1/1/04 #126 -56.89 PG&E
3 1/2/04 #127 -89.99 Safeway
4 1/3/04 +750.00 Paycheck
5 1/4/04 #128 -60.00 Visa
说明 1.在BEGIN块中,记录分隔符(RS)被赋值为空,字段分隔符(FS)被设为换行符,输出记录分隔符(ORS)则被设置为两个换行符。于是,每一行都是一个字段,且输出记录之间有两个换行符将其分隔。 2.打印记录号,后跟记录的每个字段