说明:
1,本文反复阅读、参考和总结自《AWK程序设计语言》第一章节,且对原文语句进行了不少精炼
2,目的为熟练使用awk或者有朝一日快速捡起awk的使用方法
3,本文所有原件获取方式:阿里云盘下载链接保留本文所有涉及总结源文件和文档「awk」https://www.aliyundrive.com/s/7gow7qwJnZ3
awk快速一纸通
目录
一. AWK指南
1 起步
1.1 简单示例1:
1.2 简单示例2:
1.3 程序结构
1.4 执行程序
1.5 错误诊断
2 简单输出print
2.1 内建符号$0
2.2 内建符号NF, $NF
2.3 内建符号NR
3 高级输出printf
3.1 按照格式要求输出
3.2 排序输出
4 多样化模式匹配
4.1 通过对比选择
4.2 通过计算选择
4.3 通过文本内容选择
4.4 模式组合
4.5 数据验证
4.6 BEGIN与END
5 使用AWK进行计算
5.1 使用变量计数
5.2 求和与平均值
5.3 处理文本
5.4 字符串连接
5.5 打印最后一个输入行
5.6 内置函数
6 控制语句
6.1 if-else语句
6.2 while语句
6.3 for语句
6.4 数组
有用的awk程序往往很简短,仅仅一两行。假设有一个名为 emp.data 的文件,其中包含员工的姓名、薪资(美元/小时)以及小时数,一个员工一行数据,如下所示:
Beth |
4.00 |
0 |
Dan |
3.75 |
0 |
kathy |
4.00 |
10 |
Mark |
5.00 |
20 |
Mary |
5.50 |
22 |
Susie |
4.25 |
18 |
现在打印出工作时间超过零小时的员工的姓名和工资(薪资乘以时间),输入这个命令行就可以了:
awk '$3 >0 { print $1, $2 * $3 }' emp.data
会得到如下输出:
Kathy 40
Mark 100
Mary 121
Susie 76.5
该命令行告诉系统执行引号内的awk程序,从输入文件emp.data 获取程序所需的数据。引号内的部分是个完整的awk程序,包含单个模式-动作语句。
模式: $3>0 用于匹配每行第三列大于0的输入行,
动作: 打印每个匹配行的第一个字段以及第二第三字段的乘积。
如果想打印出还没工作过的员工的姓名,则输入命令行::
awk '$3 == 0 { print $1 }' emp.data
模式:$3 == 0 匹配每行第三个字段等于0的行,
动作: 打印该行的第一个字段。
上述的两个示例命令行中,引号之间的部分是awk编程语言写的程序,即程序就是有模式和动作组成(可以只有其中一个)。本章中的每个awk程序都是一个或多个模式-动作语句的序列,实际上awk的结构灵活,可以是:
Awk ‘pattern { action }’#标准的awk形式
Awk ‘{ action }’ #没有模式的动作
Awk ‘pattern { action } pattern { action }…’
Awk ‘pattern || pattern { action }…’
Awk ‘pattern && pattern { action;action }…’
Awk ‘pattern { action }’Flie1 File2 #标准的awk形式执行多个file
Awk ‘pattern,pattern { action }’#匹配两个模式
awk -f progfile optional list of input files
其中-f选项指示awk从指定文件中获取程序。可以使用任意文件名替换progfile
awk的基本操作是一行一行地扫描输入,通过模式匹配每行并且按照action执行预期目的。
执行awk程序的方式有多种。可以执行多个文件,可以输入:
awk '$3 == 0 { print $1 }' file1 file2
打印file1和file2文件中第三个字段为0的每一行的第一个字段。
你可以省略命令行中的输入文件,仅输入:
awk ‘pattern { action }’
这种情况下,awk会将program应用于你在终端中接着输入的任意数据行,直到你输入一个文件结束信号(Unix系统上为ctrl+d)。如下是Unix系统的一个会话示例:
$ awk ‘$3 == 0 { print $1 }’
Beth 4.00 0
Beth
Dan 3.75 0
Dan
Kathy 3.75 10
Kathy 3.75 0
Kathy
...加粗的字符是计算机打印的。
这个动作非常便于调试awk:输入awk程序,然后输入数据,观察发生了什么。
如果awk程序存在错误,awk会给诊断信息。例如,如果打错了大括号,如下所示::
awk '$3 == 0 [ print $1 }' emp.data
会得到如下信息:
awk: syntax error at source line 1
context is
$3 == 0 >>> [ <<<
extra }
missing ]
awk: bailing out at source line 1
“Syntax error”意味着在 >>> <<< 标记的地方检测到语法错误。
“Bailing out”意味着没有试图恢复。有时你会得到更多的帮助-关于错误是什么,比如大括号或括弧不匹配。因为存在句法错误,awk就不会尝试执行这个程序。然而,有些错误,直到你的程序被执行才会检测出来。例如,如果你试图用零去除某个数,awk会在这个除法的地方停止处理并报告输入行的行号以及在程序中的行号
Awk 程序一次从输入文件的中读取一行内容并把它分割成一个个字段, 通常默认情况下, 一个字段是一个不包含任何空格或制表符的连续字符序列. 输入行中的第一个字段被称做$1,第二个是$2, 以此类推.整行的内容被定义为$0.每一行的字段数量可以不同.通常, 我们要做的仅仅只是打印出每一行中的某些字段, 也许还要做一些计算. 这一节的程序基本上都是这种形式.
$0该行整段内容
打印满足条件的每一行,由于 $0 表示整行,下列就表示打印第三字段大于0的所有行
awk '$3 >0 { print $0 }' emp.data
也会做一样的事情.
打印特定字段
使用一个 print 语句可以在同一行中输出不止一个字段. 下面的程序输出了每行输入中的第一和第三个字段
Awk ‘{ print $1, $3 }’ emp.data
使用 emp.data 作为输入, 它将会得到
Beth 0
Dan 0
Kathy 10
Mark 20
Mary 22
Susie 18
在 print 语句中被逗号分割的表达式, 在默认情况下他们将会用一个空格分割来输出. 每一行 print 生成的内容都会以一个换行符作为结束. 但这些默认行为都可以自定义; 我们将在第二章中介绍具体的方法.
NF该行字段数量,$NF该行最后一个字段值
很显然你可能会发现你总是需要通过$1,$2 这样来指定不同的字段,但任何表达式都可以使用在$之后来表达一个字段的序号;表达式会被求值并用于表示字段序号.Awk会对当前输入的行有多少个字段进行计数,并且将当前行的字段数量存储在一个内建的称作NF的变量中. 因此下面的程序
{ print NF, $1, $NF }
会依次打印出每一行的字段数量, 第一个字段的值, 该行最后一个字段值.
NR,该行行号;$0 该行整行内容
如何让输出看起来更漂亮?打印行号
Awk提供了另一个内建变量,叫做NR, 它会存储当前已经读取了多少行的计数. 我们可以使用 NR 和 $0 给 emp.data 的没一行加上行号:
{ print NR, $0 }
打印的输出看起来会是这样:
1 Beth 4.00 0
2 Dan 3.75 0
3 Kathy 4.00 10
4 Mark 5.00 20
5 Mary 5.50 22
6 Susie 4.25 1 8
在输出中添加内容
你当然也可以在字段中间或者计算的值中间打印输出想要的内容:
{ print "total pay for", $1, "is", $2 * $3 }
输出
total pay for Beth is 0
total pay for Dan is 0
total pay for Kathy is 40
total pay for Mark is 100
total pay for Mary is 121
total pay for Susie is 76.5
在打印语句中, 双引号内的文字将会在字段和计算的值中插入输出.
print 语句可用于快速而简单的输出。若要严格按照你所想的格式化输出,则需要使用 printf 语句。正如我将在2.4节所见, printf 几乎可以产生任何形式的输出,但在本节中,我们仅展示其部分功能。
printf 语句的形式如下::
printf(format, value1, value2, ..., valuen)
其中format是字符串格式,在format之后的每个值该如何打印的格式。一个格式必定有%符,后面跟着一些字符,用来控制特定value的格式。第一个格式用于如何打印value1,第二个格式用于说明如何打印value2,... 因此,有多少value要打印,在format中就要有多少个%格式。
这里有个程序使用printf打印每位员工的总薪酬:
{ printf("total pay for %s is $%.2f\n", $1, $2 * $3) }
printf语句中的格式字符串包含两个%格式。
第一个是%s,说明以字符串的方式打印第一个值$1。
第二个是%.2f,说明以浮点数的方式打印第二个值$2*$3,并保留小数点后面两位。
格式字符串中其他东西,包括美元符号,仅逐字打印。字符串尾部的\n代表开始新的一行,使得后续输出将从下一行开始。以emp.data为输入,该程序产生:
total pay for Beth is $0.00
total pay for Dan is $0.00
total pay for Kathy is $40.00
total pay for Mark is $100.00
total pay for Mary is $121.00
total pay for Susie is $76.50
printf不会自动产生空格或者换行,必须是你自己来创建,所以不要忘了\n 。
另一个程序是打印每位员工的姓名与薪酬::
{ printf("%-8s $%6.2f\n", $1, $2 * $3) }
第一个规格%-8s将一个姓名以字符串形式,8个字符宽度,左对齐输出(-的作用)。
第二个规格%6.2f将薪酬以数字的形式,保留小数点后两位,在6个字符宽度的字段中输出。
Beth $ 0.00
Dan $ 0.00
Kathy $ 40.00
Mark $100.00
Mary $121.00
Susie $ 76.50
假设你想打印每位员工的所有数据,包括他或她的薪酬,并以薪酬递增的方式进行排序输出。最简单的方式是使用awk将每位员工的总薪酬置于其记录之前,然后利用一个排序程序来处理awk的输出。Unix上,命令行如下(未测试成功):
awk '{ printf("%6.2f %s\n", $2 * $3, $0) }' emp.data | sort -n
将awk的输出通过管道传给 sort 命令,输出为:
0.00 Beth 4.00 0
0.00 Dan 3.75 0
40.00 Kathy 4.00 10
76.50 Susie 4.25 18
100.00 Mark 5.00 20
121.00 Mary 5.50 22
Awk的模式适合用于为进一步的处理从输入中选择相关的数据行。由于不带动作的模式会打印所有匹配模式的行,所以很多awk程序仅包含一个模式。本节将给出一些有用的模式示例。
这个程序使用一个对比模式来选择每小时赚5美元或更多的员工,也就是第二个字段大于等于5的行:
awk '$2 >= 5{print $0}' emp.data
从 emp.data 中选出这些行::
Mark 5.00 20
Mary 5.50 22
awk '$2 * $3 > 50 { printf("$%.2f for %s\n", $2 * $3, $1) }' emp.data
打印出总薪资超过50美元的员工的薪酬。
除了数值测试,你还可以选择包含特定单词或短语的输入行。这个程序会打印所有第一个字段为 Susie 的行::
$1 == "Susie"
操作符 == 用于测试相等性。你也可以使用称为 正则表达式 的模式查找包含任意字母组合,单词或短语的文本。这个程序打印任意位置包含 Susie 的行::
/Susie/
输出为这一行::
Susie 4.25 18
正则表达式可用于指定复杂的多的模式;2.1节将会有全面的论述。
可以使用括号和逻辑操作符与&&(两个条件都满足),或||(满足其中一个或者两个),以及非!(不满足)对模式进行组合。程序:
$2 >= 4 || $3 >= 20 #两个条件都满足的行只打印一次
会打印 $2 (第二个字段) 大于等于 4 或者 $3 (第三个字段) 大于等于 20 的行::
Beth 4.00 0
kathy 4.00 10
Mark 5.00 20
Mary 5.50 22
Susie 4.25 18
与如下包含两个模式程序相比::
awk '$2 >=4, $3 >= 50{print $0}' emp.data
如果某个输入行两个条件都满足,这个程序会打印它两遍::
Beth 4.00 0
Kathy 4.00 10
Mark 5.00 20
Mark 5.00 20
Mary 5.50 22
Mary 5.50 22
Susie 4.25 18
注意如下程序:
!($2 < 4 && $3 < 20)
会打印既不满足$2小于4也不满足$3小于20的行;这个条件与上面第一个模式组合等价,虽然也许可读性差了点。
实际的数据中总是会存在错误的。在数据验证-检查数据的值是否合理以及格式是否正确-方面,Awk是个优秀的工具。
数据验证本质上是否定的:不是打印具备期望属性的行,而是打印可疑的行。如下程序使用对比模式 将5个数据合理性测试应用于emp.data的每一行::
NF != 3 { print $0, "number of fields is not equal to 3" }
$2 < 3.35 { print $0, "rate is below minimum wage" }
$2 > 10 { print $0, "rate exceeds $10 per hour" }
$3 < 0 { print $0, "negative hours worked" }
$3 > 60 { print $0, "too many hours worked" }
如果没有错误,则没有输出。
特殊模式BEGIN 模式指定了处理文本之前需要执行的操作,END模式指定了处理文本之后需要执行的操作。这个程序使用BEGIN来输出一个标题::
awk 'BEGIN { print "Name RATE HOURS";print ""} {print}' emp.data输出为::
NAME RATE HOURS
Beth 4.00 0
Dan 3.75 0
Kathy 4.00 10
Mark 5.00 20
Mary 5.50 22
Susie 4.25 18
程序的动作部分你可以在一行上放多个语句,不过要使用分号进行分隔。注意普通的print是打印当前输入行,与之不同的是print“”会打印一个空行。
一个动作就是一个以新行或者分号分隔的语句序列。你已经见过一些其动作仅是单个 print 语句的例子。本节将提供一些执行简单的数值以及字符串计算的语句示例。在这些语句中,你不仅可以使用像 NF 这样的内置变量,还可以创建自己的变量用于计算、存储数据诸如此类的操作。awk中,用户创建的变量不需要声明。
这个程序使用一个变量 emp 来统计工作超过15个小时的员工的数目:
awk '$3 > 15 {emp = emp +1} END {print emp ," employees worked more than 15 hours "}' emp.data
对于第三个字段超过15的每行, emp 的前一个值加1。以 emp.data 为输入,该程序产生:
3 employees worked more than 15 hours
用作数字的awk变量的默认初始值为0,所以我们不需要初始化 emp 。
为计算员工的数目,我们可以使用内置变量NR,它保存着到目前位置读取的行数;在所有输入的结尾它的值就是所读的所有行数。
END { print NR, "employees" }
输出为::
6 employees
如下是一个使用 NR 来计算薪酬均值的程序::
{ pay = pay + $2 * $3 }
END { print NR, "employees"
print "total pay is", pay
print "average pay is", pay/NR
}
具体的命令为 awk '{ pay = pay + $2 * $3 } END { print NR, "employees"}END {print "total pay is", pay} END {print "average pay is", pay/NR }' emp.data
第一个动作累计所有员工的总薪酬。 END 动作打印出
6 employees
total pay is 337.5
average pay is 56.25
很明显,printf 可用来产生更简洁的输出。并且该程序也有个潜在的错误:在某种不太可能发生的情况下,NR等于0,那么程序会试图执行零除,从而产生错误信息。
awk的优势之一是能像大多数语言处理数字一样方便地处理字符串。awk变量可以保存数字也可以保存字符串。这个程序会找出时薪最高的员工:
awk '$2 > maxrate { maxrate = $2; maxemp = $1 } END { print "highest hourly rate:", maxrate, "for", maxemp }' emp.data
$2 > maxrate { maxrate = $2; maxemp = $1 }
END { print "highest hourly rate:", maxrate, "for", maxemp }
输出
highest hourly rate: 5.50 for Mary
这个程序中,变量 maxrate 保存着一个数值,而变量 maxemp 则是保存着一个字符串。(如果有几个员工都有着相同的最大时薪,该程序则只找出第一个。)
可以合并老字符串来创建新字符串。这种操作称为 连接(concatenation) 。程序
awk '{ names = names $1 " "}END { print names }' emp.data
{ names = names $1 " "}
END { print names }
通过将每个姓名和一个空格附加到变量 names 的前一个值, 来将所有员工的姓名收集进单个字符串中。最后 END 动作打印出 names 的值::
Beth Dan Kathy Mark Mary Susie
awk程序中,连接操作的表现形式是将字符串值一个接一个地写出来。对于每个输入行,程序的第一个语句先连接三个字符串:names(初始值为空)的前一个值、当前行的第一个字段以及一个空格,然后将得到的字符串赋值给names。因此,读取所有的输入行之后,names 就是个字符串,包含所有员工的姓名,每个姓名后面跟着一个空格。用于保存字符串的变量的默认初始值是空字符串(也就是说该字符串包含零个字符),因此这个程序中的names不需要显式初始化。
虽然在 END 动作中 NR 还保留着它的值,但 $0 没有。程序
awk '{ last = $0 }END { print last }' emp.data
{ last = $0 }
END { print last }
是打印最后一个输入行的一种方式::
Susie 4.25 18
我们已看到awk提供了内置变量来保存某些频繁使用的数量,比如:字段的数量和输入行的数量。类似地,也有内置函数用来计算其他有用的数值。除了平方根、对数、随机数诸如此类的算术函数,也有操作文本的函数。其中之一是length,计算一个字符串中的字符数量。例如,这个程序会计算每个人的姓名的长度::
{ print $1, length($1) }
//awk '{ print $1, length($1) }' emp.data
结果::
Beth 4
Dan 3
Kathy 5
Mark 4
Mary 4
Susie 5
行、单词以及字符的计数
这个程序使用了 length 、 NF 、以及 NR 来统计输入中行、单词以及字符的数量。为了简便,我们将每个字段看作一个单词。
{ nc = nc + length($0) + 1
nw = nw + NF
}
END { print NR, "lines,", nw, "words,", nc, "characters" }
//具体如下:awk '{ nc = nc + length($0) + 1} {nw = nw + NF} END { print NR, "lines,", nw, "words,", nc, "characters" }' emp.data
文件 emp.data 有:
6 lines, 18 words, 77 characters
$0并不包含每个输入行的末尾的换行符,所以我们要另外加个1。
Awk为选择提供了一个 if-else 语句,以及为循环提供了几个语句,所以都效仿C语言中对应的控制语句。它们仅可以在动作中使用。
如下程序将计算时薪超过6美元的员工的总薪酬与平均薪酬。它使用一个 if 来防范计算平均薪酬时的零除问题。
$2 > 6 { n = n + 1; pay = pay + $2 * $3 }
END { if (n > 0)
print n, "employees, total pay is", pay,
"average pay is", pay/n
else
print "no employees are paid more than $6/hour"
}
实际代码如下
awk '$2 > 6 { n = n + 1; pay = pay + $2 * $3 }
END { if (n > 0)
print n, "employees, total pay is", pay,
"average pay is", pay/n
else
print "no employees are paid more than $6/hour"
}' emp.data
emp.data 的输出是::
no employees are paid more than $6/hour
if-else 语句中,if 后的条件会被计算。如果为真,执行第一个 print 语句。否则,执行第二个 print 语句。注意我们可以使用一个逗号将一个长语句截断为多行来书写。
一个while语句有一个条件和一个执行体。条件为真时执行体中的语句会被重复执行。这个程序使用公式value=amount(1+rate)yearsvalue=amount(1+rate)years
来演示以特定的利率投资一定量的钱,其数值是如何随着年数增长的。
# interest1 - 计算复利
# 输入: 钱数 利率 年数
# 输出: 复利值
{ i = 1
while (i <= $3) {
printf("\t%.2f\n", $1 * (1 + $2) ^ i)
i = i + 1
}
}
条件是while后括弧包围的表达式;循环体是条件后大括号包围的两个表达式。printf规格字符串中的\t代表制表符;^是指数操作符。从#开始到行尾的文本是注释,会被awk忽略,但能帮助程序的读者理解程序做的事情。
你可以为这程序输入三个一组的数字,看看不一样的钱数、利率、以及年数会产生什么。例如,如下事务演示了1000美元,利率为6%与12%,5年的复利分别是如何增长的::
$ awk -f interest1
1000 .06 5
1060.00
1123.60
1191.02
1262.48
1338.23
1000 .12 5
1120.00
1254.40
1404.93
1573.52
1762.34
另一个语句,for,将大多数循环都包含的初始化、测试、以及自增压缩成一行。如下是之前利息计算的for版本::
# interest1 - 计算复利
# 输入: 钱数 利率 年数
# 输出: 每年末的复利
{ for (i = 1; i <= $3; i = i + 1)
printf("\t%.2f\n", $1 * (1 + $2) ^ i)
}
初始化i = 1只执行一次。接下来,测试条件i <= $3;如果为真,则执行循环体的printf语句。循环体执行结束后执行自增i = i + 1,接着由另一次条件测试开始下一个循环迭代。代码更加紧凑,并且由于本循环体仅是一行语句,所以不需要大括号来包围它。
awk为存储一组相关的值提供了数组。虽然数组给予了awk很强的能力,但在这里我们仅展示一个简单的例子。如下程序将按行逆序打印输入。第一个动作将输入行存为数组 line 的连续元素;即第一行放在 line[1] ,第二行放在 line[2] , 依次继续。 END 动作使用一个 while 语句从后往前打印数组中的输入行::
# 反转 - 按行逆序打印输入
{ line[NR] = $0 } # 记下每个输入行
END { i = NR # 逆序打印
while (i > 0) {
print line[i]
i = i - 1
}
}
以 emp.data 为输入,输出为
Susie 4.25 18
Mary 5.50 22
Mark 5.00 20
Kathy 4.00 10
Dan 3.75 0
Beth 4.00 0
如下是使用 for 语句实现的相同示例::
# 反转 - 按行逆序打印输入
{ line[NR] = $0 } # 记下每个输入行
END { for (i = NR; i > 0; i = i - 1)
print line[i]
}