第八章 条件、循环、数组

条件语句

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
表达式 ? 为真执行 : 为假执行

示例,替换上面及格判断:

1
grade = (avg >= 65) ? "Pass" : "Fail"

提示:可以嵌套,但嵌套之后可读性很差,不推荐。建议条件加括号。

循环

awk提供三种循环:while、do‑while、for。

While循环

先判断条件,条件为真才执行循环体;条件为假一次都不跑。 语法:

1
2
while(条件)
    动作

多条语句必须大括号包裹。

示例:遍历一行所有字段

1
2
3
4
5
i = 1
while ( i <= NF ) {
    print $i
    ++i
}

Do循环(do‑while)

⭐重点区别:循环体至少执行1次,执行完再判断条件。 语法:

1
2
3
do
    动作
while (条件)

示例:变量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(作用于循环内部)

  1. break:立刻跳出整个循环,结束循环。
1
2
3
4
5
6
7
#找到第一个等于y的字段,打印后直接跳出循环
for ( x = 1; x <= NF; ++x ) {
    if ( y == $x ) {
        print x, $x
        break
    }
}
  1. 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(变量 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")
    }
}

判断模拟多维数组下标是否存在,括号不能丢:

1
if ((i, j) in array)

如果要取出拼接的各个下标,用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 $*

第八章核心速记清单(复习)

  1. if‑else写在动作大括号内;?:三元运算符简化简单if‑else。
  2. 循环:
    • while:先判断,再执行;条件假一次都不执行。
    • do‑while:先执行循环体,后判断,至少跑1次。
    • for(初始化;条件;自增):适合遍历字段for(i=1;i<=NF;i++)。
  3. 循环控制:
    • break:彻底跳出整个循环。
    • continue:结束本轮循环,进入下一轮。
  4. 主循环控制:
    • next:放弃当前行后续脚本,立刻读下一行。
    • exit:结束主循环,执行END块;exit N设置退出码。
  5. awk全部是关联数组:arr[key]=val;key可以数字/字符串,内部全部存字符串;数字字符串下标4和"04"不相等。
  6. for(变量 in 数组)遍历关联数组,顺序随机,不能依赖顺序。
  7. key in arr:判断下标是否存在;只判断下标,不是判断值。
  8. delete arr[key]:删除数组元素。
  9. split(str,arr,sep):切割字符串到数组,返回元素数量,数组下标从1开始。
  10. 模拟多维数组:arr[i,j],内部通过SUBSEP拼接成字符串键。
  11. 内置数组
    • ARGV/ARGC:命令行参数,下标从0开始。
    • ENVIRON:操作系统环境变量。