The awk programming language, Chapter1 Note
中文版参考 https://github.com/wuzhouhui/awk
目录
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
现在你想打印每位雇员的名字以及他们的报酬(每小时工资乘以工作时长), 而
(1)打印工作时长大于零的雇员及其报酬
awk '$3 > 0 { print $1, $2 * $3 }' emp.data
Output:
Kathy 40
Mark 100
Mary 121
Susie 76.5
说明:
运行的程序用单引号包围起来,从输入文件emp.data 获取数据
运行的程序由一个单独的模式-动作语句(pattern-action statement) 组成
模式 "$3 > 0" 扫描每一个输入行,如果该行的第三列大于零, 则动作
awk 的基本操作 陆续地扫描每一行, 搜索可以被模式匹配的行;每匹配一个模式 => 动作执行
awk 程序是由一个或多个模式-动作语句组成的序列;在一个模式–动作语句中, 模式或动作可以省略其一, 但不能两者同时被省略
运行AWK程序
运行一个awk 程序有多种方式
(1)awk 'program' <input files...>
(2)也可以在命令行上省略输入文件, 只要键入
awk 'program'
在这种情况下, awk 会将program 应用到你接下来在终端输入的内容上面, 直到键入一个文件结束标志(Unix系统是组合键Control-d)
# awk '$3 > 0 { print $1, $2 * $3 }'
Beth 4.00 0
Dan 3.75 0
Kathy 4.00 10
Kathy 40 --> 终端输出
Mark 5.00 20
Mark 100 --> 终端输出
...
(3)如果程序比较长, 可以将其放在一个文件中
programfile内容为
$3 > 0 { print $1, $2 * $3 }
执行
# awk -f programfile emp.data
输出
Kathy 40
Mark 100
Mary 121
Susie 76.5
说明:选项-f --> awk 从文件中提取程序
输出
简单的输出print
(1)打印每一行: { print } 或 { print $0 }
# awk '{ print }' emp.data 或者
# awk '{ print $0 }' 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
说明: $0 表示一整行
(2)打印某些字段
{ print $1, $3 }
==> 打印第一个和第三个字段
说明:在print 语句中由逗号分隔的表达式, 在输出时默认用一个空格符分隔; 由print 打印的每一行都由一个换行符终止
(3)NF, 字段的数量
(4)NR, 行号
(5)将文本放入输出中
# awk '{ print NR, "total pay for", $1, "is", $2 * $NF }' emp.data
输出
1 total pay for Beth is 0
2 total pay for Dan is 0
3 total pay for Kathy is 40
4 total pay for Mark is 100
5 total pay for Mary is 121
6 total pay for Susie is 76.5
格式化输出printf
# awk '{ printf("total pay for %s is $%.2f\n", $1, $2 * $3) }' 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 不会自动产生空格符或换行符
# awk '{ printf("%-8s $%6.2f\n", $1, $2 * $3) }' emp.data
输出
Beth $ 0.00
Dan $ 0.00
Kathy $ 40.00
Mark $100.00
Mary $121.00
Susie $ 76.50
说明:
%-8s, 将名字左对齐输出, 占用8个字符的宽度
%6.2f, 将报酬以带有两位小数的数值格式打印出来, 占用6个字符的宽度
排序输出
# awk '{ printf("%6.2f | %s\n", $2 * $3, $0) }' emp.data
输出
0.00 | Beth 4.00 0
0.00 | Dan 3.75 0
40.00 | Kathy 4.00 10
100.00 | Mark 5.00 20
121.00 | Mary 5.50 22
76.50 | Susie 4.25 18
选择
通过文本内容选择
# awk '$1 == "Susie"' emp.data 或者使用正则表达式
# awk '/Susie/' emp.data
输出
Susie 4.25 18
模式的组合
逻辑运算符包括 &&, ||, 和 !
# awk '$2 >= 4 && $2 * $3 >= 50' emp.data
Mark 5.00 20
Mary 5.50 22
Susie 4.25 18
# awk '$2 >= 4 || $2 * $3 >= 50' emp.data 说明:两个条件都满足的行只输出一次
Beth 4.00 0
Kathy 4.00 10
Mark 5.00 20
Mary 5.50 22
Susie 4.25 18
# awk '$2 >= 4 {print} $2 * $3 >= 50 {print} ' 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
Susie 4.25 18
数据验证
数据验证在本质上是否定: 打印可疑行
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 在第一个输入文件的第一行之前匹配
END 在最后一个输入文件的最后一行被处理之后匹配
例如,输出数据前打印表头
# 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
说明:可以在同一行放置多个语句, 语句之间用分号分开
用AWK计算
计数
# cat countWorkUp15
$3 > 15 { emp = emp + 1 }
END { print emp, "employees worked more than 15 hours" }
# awk -f countWorkUp15 emp.data
3 employees worked more than 15 hours
说明:当awk 的变量作为数值使用时, 默认初始值为0, 所以无需初始化emp
计算总和与平均数
# cat averPay
{ pay = pay + $2 * $3 }
END { print NR, "employees"
print "total pay is", pay
print "average pay is", pay / NR
}
# awk -f averPay emp.data
6 employees
total pay is 337.5
average pay is 56.25
取最大值
# cat maxHourRate
$2 > maxrate { maxrate = $2; maxemp = $1 }
END { print "highest hourly rate:", maxrate, "for", maxemp }
# awk -f maxHourRate emp.data
highest hourly rate: 5.50 for Mary
说明:如果有多个雇员都拥有相同的最高每小时工资, 这个程序只会打印第一个人的名字
字符串拼接
# awk '{ names = names $1 " " } END { print names }' emp.data
输出
Beth Dan Kathy Mark Mary Susie
打印最后一行
# awk 'END { print last }' emp.data 说明: 输出是空行
# awk '{ last = $0 } END { print last }' emp.data
Susie 4.25 18
说明:在END 动作里, NR的值保留了下来, $0的值没有保留
内建函数length
# cat -A emp.data
Beth^I4.00^I0$
Dan^I3.75^I0$
Kathy^I4.00^I10$
Mark^I5.00^I20$
Mary^I5.50^I22$
Susie^I4.25^I18$
# awk '{ print $0,"--", length($0) }' emp.data
Beth 4.00 0 -- 11
Dan 3.75 0 -- 10
Kathy 4.00 10 -- 13
Mark 5.00 20 -- 12
Mary 5.50 22 -- 12
Susie 4.25 18 -- 13
说明:$0 不包含换行符
流程控制语句
If-Else 语句
# cat test1
$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 -f test1 emp.data
输出
no employees are paid more than $6/hour
While 语句
价值计算的公式
value.txt内容如下
# interest1 - compute compound interest
# input: amount rate years
# output: compounded value at the end of each year
{ i = 1
while (i <= $3) {
printf("\t%.2f\n", $1 * (1 + $2) ^ i)
i = i + 1
}
}
键入三个数, 看一下不同的本金, 利率和时间会产生怎样的结果
例如, $1000 在6% 与12% 的利率下, 在5 年的时间的升值如下
# awk -f value.txt
1000 0.06 5
1060.00
1123.60
1191.02
1262.48
1338.23
1000 0.12 5
1120.00
1254.40
1404.93
1573.52
1762.34
For 语句
可以实现与上例相同的功能
value2.txt内容如下
# interest2 - compute compound interest
# input: amount rate years
# output: compounded value at the end of each year
{ for (i = 1; i <= $3; i = i + 1)
printf("\t%.2f\n", $1 * (1 + $2) ^ i)
}
# echo "1000 0.06 5" > test
# awk -f value2.txt test
1060.00
1123.60
1191.02
1262.48
1338.23
数组
如下实现了从最后一行倒着打印输入文件
reversePrint内容如下
# reverse - print input in reverse order by line
{ line[NR] = $0 } # remember each input line
END { i = NR # print lines in reverse order
while (i > 0) {
print line[i]
i = i - 1
}
}
# awk -f reversePrint 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
输入文件countries内容如下
USSR 8649 275 Asia
Canada 3852 25 North America
China 3705 1032 Asia
USA 3615 237 North America
Brazil 3286 134 South America
India 1267 746 Asia
Mexico 762 78 North America
France 211 55 Europe
Japan 144 120 Asia
Germany 96 61 Europe
England 94 56 Europe
程序格式
模式–动作语句, 以及动作内的语句通常用换行符分隔; 如果出现在同一行, 用分号分隔
动作的左花括号必须与它的模式在同一行; 而剩下的部分包括右花括号, 可以在下面几行
一条长语句可以分散成多行, 只要在断行处插入一个反斜杠"\"即可:
模式
1. BEGIN{ statements}
在输入被读取之前, statements 执行一次
2. END{ statements}
当所有输入被读取完毕之后, statements 执行一次
3. expression{ statements}
每碰到一个使expression 为真的输入行, statements 就执行
4. /regular expression/ { statements}
输入行含有一段字符串, 而该字符串可以被regular expression 匹配, statements 就执行
5. compound pattern { statements}
一个复合模式将表达式用&&(AND), ||(OR), !(NOT), 以及括号组合起来; 当compound pattern 为真时, statements 执行
6. pattern1, pattern2 { statements}
一个范围模式匹配多个输入行, 这些输入行从匹配pattern1 的行开始, 到匹配pattern2 的行结束(包括这两行), 对这其中的每一行执行statements
说明:
BEGIN 与END 不与其他模式组合, 且不能省略动作
一个范围模式不能是其他模式的一部分
设置字段分隔符
BEGIN 的一个常见用途是更改输入行被分割为字段的默认方式, 分割字符由一个内建变量FS 控制
# cat countries2
USSR|8649|275|Asia
Canada|3852|25|North America
China|3705|1032|Asia
USA|3615|237|North America
Brazil|3286|134|South America
India|1267|746|Asia
Mexico|762|78|North America
France|211|55|Europe
Japan|144|120|Asia
Germany|96|61|Europe
England|94|56|Europe
# awk 'BEGIN{FS="|"} {printf("%10s %s %s\n", $1,"-->",$NF)}' countries2
输出
USSR --> Asia
Canada --> North America
China --> Asia
USA --> North America
Brazil --> South America
India --> Asia
Mexico --> North America
France --> Europe
Japan --> Asia
Germany --> Europe
England --> Europe
字符串匹配模式
1. /regexpr/
当前输入行包含一段能够被regexpr 匹配的子字符串时, 该模式被匹配
2. expression ~ /regexpr/
如果expression 的字符串值包含一段能够被regexpr 匹配的子字符时, 该模式被匹配
3. expression !~ /regexpr/
如果expression 的字符串值不包含能够被regexpr 匹配的子字符串, 该模式被匹配
# awk '/America/ {print}' countries
Canada 3852 25 North America
USA 3615 237 North America
Brazil 3286 134 South America
Mexico 762 78 North America
# awk '$1 ~ /an/ {print $1}' countries
Canada
France
Japan
Germany
England
# awk '$0 !~ /A/ {print $1}' countries
France
Germany
England
范围模式
一个范围模式由两个被逗号分开的模式组成
pat1, pat2
一个范围模式匹配多个输入行, 这些输入行从匹配pat1 的行开始, 到匹配pat2 的行结束, 包括这两行
# awk '/Canada/,/USA/ {print $0}' countries
Canada 3852 25 North America
China 3705 1032 Asia
USA 3615 237 North America
pat2 可以与pat1 匹配到同一行, 这时候模式的范围大小就退化到了一行
# awk '/Mexico/,/America/ {print $0}' countries
Mexico 762 78 North America
一旦范围的第一个模式匹配到了某个输入行, 那么整个范围模式的匹配就开始了; 如果范围模式的第二个模式一直都没有匹配到某个输入行, 那么范围模式会一直匹配到输入结束
# awk '/Europe/,/Africa/ {print $0}' countries
France 211 55 Europe
Japan 144 120 Asia
Germany 96 61 Europe
England 94 56 Europe
打印每一个输入文件的前5 行, 并在每一行的左边加上文件名
说明:两个内建变量,变量FNR 表示从当前输入文件中, 到目前为止读取到的行数, 变量FILENAME表示当前输入文件名
# awk 'FNR == 1, FNR == 5 { print FILENAME ": " $0 }' countries
countries: USSR 8649 275 Asia
countries: Canada 3852 25 North America
countries: China 3705 1032 Asia
countries: USA 3615 237 North America
countries: Brazil 3286 134 South America