第八章 条件、循环、数组
条件语句
if‑else写在awk动作大括号{}内部(区别于第七章直接写在主循环外的模式匹配)。
语法:
1
2
3
4
|
if(表达式)
动作1
[else
动作2]
|
[ ]代表可选,可省略,只出现在手册说明,禁止写到代码
- 表达式结果非零 / 非空字符串 = 真;零 / 空字符串 = 假
- 如果动作包含多条语句,必须用大括号包裹
- 大括号换行、空格不强制,建议缩进提升可读性
示例1:简单if
1
2
3
|
if(x) print x # x不为0/不为空就打印x
if(x == y) print x # 判断相等,==判断,=是赋值
if(x ~ /[yY]es/) { print x } # 正则匹配
|
多语句必须加大括号:
1
2
3
4
|
if(表达式){
语句1
语句2
}
|
示例2:if‑else 判断及格不及格
1
2
3
4
|
if (avg >= 65)
grade = "Pass"
else
grade = "Fail"
|
示例3:多分支 else‑if(成绩等级)
规则:从上往下判断,只要某个条件为真就执行,剩余分支全部跳过;全部不满足执行最后的else。
1
2
3
4
5
|
if (avg >= 90) grade = "A"
else if (avg >= 80) grade = "B"
else if (avg >= 70) grade = "C"
else if (avg >= 60) grade = "D"
else grade = "F"
|
条件操作符(三元运算符 ?:)
等价简化简单if‑else。
语法:
示例,替换上面及格判断:
1
|
grade = (avg >= 65) ? "Pass" : "Fail"
|
提示:可以嵌套,但嵌套之后可读性很差,不推荐。建议条件加括号。
循环
awk提供三种循环:while、do‑while、for。
While循环
先判断条件,条件为真才执行循环体;条件为假一次都不跑。
语法:
多条语句必须大括号包裹。
示例:遍历一行所有字段
1
2
3
4
5
|
i = 1
while ( i <= NF ) {
print $i
++i
}
|
Do循环(do‑while)
⭐重点区别:循环体至少执行1次,执行完再判断条件。
语法:
示例:变量x初始值awk默认为0
1
2
3
4
5
6
|
BEGIN {
do {
++x
print x
} while (x <= 4 )
}
|
输出:1 2 3 4 5
流程:先++x打印,再判断条件,所以会多跑一轮。
业务示例:累加字段,总和超过100就停止读取字段
1
2
3
4
5
6
7
8
|
{
total = i = 0
do {
++i
total += $i
} while (total <= 100 )
print i, ":", total
}
|
For循环
适合已知循环起止;把【初始化、条件判断、自增】写在一处,代码紧凑。
语法:
1
2
|
for(初始化计数器; 判断条件; 计数器增减)
动作
|
示例1:遍历本行全部字段,从第1到最后
1
2
|
for( i = 1; i <= NF; i++)
print $i
|
示例2:反向遍历字段(从最后一个到第一个)
1
2
|
for( i = NF; i >= 1; i--)
print $i
|
示例3:通用成绩求平均值,兼容任意数量成绩;$1是姓名,从$2开始是分数
1
2
3
4
|
total = 0
for (i = 2; i <= NF; ++i)
total += $i
avg = total / (NF - 1)
|
注意avg = total / (NF‑1),括号不能丢;除法优先级高于减法。
求阶乘完整示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
awk '
BEGIN {
printf("Enter number: ")
}
$1 ~ /^[0-9]+$/ {
number = $1
if (number == 0)
fact = 1
else
fact = number
for (x = number -1 ; x >1; x--)
fact *= x
printf("The factorial of %d is %g \n", number, fact)
exit
}
{ printf("\nInvalid entry.Enter a number: ") }
' -
|
-代表读取标准输入;exit退出脚本;%g自动适配大数科学计数。
影响流控制的其他语句
break / continue(作用于循环内部)
break:立刻跳出整个循环,结束循环。
1
2
3
4
5
6
7
|
#找到第一个等于y的字段,打印后直接跳出循环
for ( x = 1; x <= NF; ++x ) {
if ( y == $x ) {
print x, $x
break
}
}
|
continue:终止本次循环,直接跳到循环开头进入下一轮,不退出循环。
1
2
3
4
5
|
#跳过第3个字段不打印
for ( x = 1; x <= NF; ++x ) {
if ( x == 3 ) continue
print x, $x
}
|
next(作用于awk主输入循环,不是for/while循环)
next:停止处理当前这一行剩余所有脚本,立刻读取下一条输入记录,回到脚本最顶端。
1
2
3
4
5
|
FILENAME == "acronyms"{
action
next
}
{ print }
|
读取acronyms文件各行执行action,执行next;不会走到后面{print};其他文件才执行print。
exit
exit:立刻终止主输入循环,去执行END{}块;如果在END中执行exit,直接结束程序。
exit 数字:设置awk退出返回码。
1
2
3
4
|
{
exit 5
}
END { exit }
|
最终退出状态码是5。
数组
awk全部数组都是关联数组,下标可以数字,也可以字符串。语法 数组[下标] = 值。
不需要预先声明数组大小,直接赋值即可。
简单数字下标示例:
1
2
|
flavor[1] = "cherry"
print flavor[1]
|
示例:保存每个学生平均分,NR记录行号做数组下标;在END块统计全班成绩
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
{
student_avg[NR] = avg
}
END {
for (x = 1; x <= NR; x++)
class_avg_total += student_avg[x]
class_average = class_avg_total / NR
for ( x = 1; x <= NR; x++)
if (student_avg[x] >= class_average)
++above_average
else
++below_average
print "Class Average:", class_average
print "At or Above Average: ", above_average
print "Below Average:",below_average
}
|
关联数组
awk 的数组全部是关联数组(键‑值字典),没有真正的数字下标。
不管你写的下标看起来是数字、还是变量,awk 在底层内部,一律转成字符串保存,当做字典的 key(键)。
flavor[4] 和 flavor["04"] 是两个完全不同下标,互不相等!
- 普通编程语言数组下标只是序号;awk关联数组下标是键‑值映射。
假设输入文本(acronyms 词典文件):
1
2
|
BASIC Beginner's All‑Purpose Symbolic Instruction Code
CICS Customer Information Control System
|
字符串下标示例(缩写词词典)
1
2
|
acro[$1] = $2
print acro["BASIC"]
|
含义:把$1的值当做数组下标(key,键),把$2的值当做数组**值(value)**存进去。
for‑in循环(专门遍历关联数组)
注意:遍历顺序是随机,不保证插入顺序、数字大小顺序!
语法:
变量依次拿到数组每一个下标;用数组[变量]取对应值。
1
2
|
for ( item in acro )
print item, acro[item]
|
测试数组下标是否存在 in 操作符
下标 in 数组:判断下标是否存在;不能用来判断值是否存在!
1
2
|
if ("BASIC" in acro)
print "Found BASIC"
|
区分:赋值为空acro["BASIC"]="",下标仍然存在,in返回真;delete删除之后in返回假。
delete 删除数组元素
1
|
delete array[subscript]
|
删除指定下标;删除后in测试返回false。
只是赋值空字符串不等于删除。
split() 内置函数:字符串切分为数组
语法:
1
|
n = split(字符串, 数组名, 分隔符)
|
- 返回值
n:切分之后元素总个数;数组下标从1开始。
- 分隔符可以是正则;省略第三个参数默认使用FS。
示例:拆分姓名字符串
1
2
3
|
z = split($1, fullname, " ")
print fullname[1] #名字
print fullname[z] #姓氏
|
罗马数字转换小例子(BEGIN内拆分字符串构建数组)
1
2
3
4
5
6
7
8
|
BEGIN {
split("I,II,III,IV,V,VI,VII,VIII,IX,X", numerals, ",")
}
$1 > 0 && $1 <= 10 {
print numerals[$1]
exit
}
{ print "invalid number" }
|
模拟多维数组
awk没有真正多维数组;语法arr[i,j]是模拟。
- 内部使用系统内置变量
SUBSEP(不可见特殊字符)把多个下标拼接成一个字符串作为一维数组的键。
bitmap[i,j] 等价 bitmap[i SUBSEP j]。
使用示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
BEGIN { FS = ","
WIDTH = 12
HEIGHT = 12
for (i = 1; i <= WIDTH; ++i)
for (j = 1; j <= HEIGHT; ++j)
bitmap [i,j] = "O"
}
{
bitmap [$1,$2] = "X"
}
END {
for (i = 1; i <= WIDTH; ++i) {
for (j = 1; j <= HEIGHT; ++j)
printf("%s", bitmap[i,j] )
printf("\n")
}
}
|
判断模拟多维数组下标是否存在,括号不能丢:
如果要取出拼接的各个下标,用split(item, subscr, SUBSEP)拆分。
awk内置特殊数组
ARGV / ARGC 命令行参数数组
ARGC:参数总个数
ARGV[]:命令行参数数组;下标从0开始。ARGV[0]是awk程序名。
-f、‑v这类选项不会进入ARGV数组;文件名、伪赋值参数var=10会进入ARGV。
示例打印全部命令行参数
1
2
3
4
5
|
BEGIN {
for (x = 0; x < ARGC; ++x)
print ARGV[x]
print ARGC
}
|
可以delete ARGV[n]删除元素,用来阻止awk把该参数当做文件打开。修改ARGC可以改变awk读取参数数量。
ENVIRON 环境变量数组
1
2
3
4
|
BEGIN {
for (env in ENVIRON)
print env "=" ENVIRON[env]
}
|
脚本内部修改ENVIRON["XXX"],只在awk内部生效,不会改变操作系统真实环境变量。
完整实战脚本示例(原书)
lookup 词汇查询脚本
读取词汇表文件,之后交互输入术语,查询释义;输入q/quit退出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
# lookup -- 读取本地的术语文件并提示用户查询
BEGIN { FS = "\t"; OFS = "\t"
printf("Enter a glossary term: ")
}
#读取glossary词汇文件,存入数组
FILENAME == "glossary" {
entry[$1] = $2
next
}
#匹配退出命令
$0 ~ /^(quit|[qQ]|exit|[Xx])$/ { exit }
#查询逻辑
$0 != "" {
if ($0 in entry ){
print entry[$0]
}else
print $0 " not found"
}
#继续提示输入
{
printf("Enter another glossary term (q to quit): ")
}' glossary -
|
-表示读取标准输入,接收用户键盘输入。
awkro 缩写词扩展脚本
读取缩写词典文件,把文本文件中缩写替换为全称(缩写)格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#awkro-扩展的首字母缩写词
FILENAME == "acronyms" {
split($0, entry, "\t")
acro[entry[1]] = entry[2]
next
}
/[A-Z][A-Z]+/ {
for (i = 1; i <= NF; i++)
if ( $i in acro ) {
$i = acro[$i] "(" $i ")"
}
}
{
print $0
}' acronyms $*
|
第八章核心速记清单(复习)
if‑else写在动作大括号内;?:三元运算符简化简单if‑else。
- 循环:
while:先判断,再执行;条件假一次都不执行。
do‑while:先执行循环体,后判断,至少跑1次。
for(初始化;条件;自增):适合遍历字段for(i=1;i<=NF;i++)。
- 循环控制:
break:彻底跳出整个循环。
continue:结束本轮循环,进入下一轮。
- 主循环控制:
next:放弃当前行后续脚本,立刻读下一行。
exit:结束主循环,执行END块;exit N设置退出码。
- awk全部是关联数组:
arr[key]=val;key可以数字/字符串,内部全部存字符串;数字字符串下标4和"04"不相等。
for(变量 in 数组)遍历关联数组,顺序随机,不能依赖顺序。
key in arr:判断下标是否存在;只判断下标,不是判断值。
delete arr[key]:删除数组元素。
split(str,arr,sep):切割字符串到数组,返回元素数量,数组下标从1开始。
- 模拟多维数组:
arr[i,j],内部通过SUBSEP拼接成字符串键。
- 内置数组
ARGV/ARGC:命令行参数,下标从0开始。
ENVIRON:操作系统环境变量。