shell编程之awk命令详解
1. awk 教程
1.1 调用 awk
awk 是一种强大的文本处理工具,在 Linux 系统中广泛应用于日志分析、数据处理等场景。调用 awk 主要有以下三种方式:
1.1.1 命令行方式
基本语法为:
awk (-F filed-separator) 'commands' input-files
其中,-F
用于指定分隔符,默认情况下,awk 以空格或制表符作为分隔符。commands
是 awk 的命令,input-files
则是要处理的文件。例如,我们有一个文件data.txt
,内容如下:
apple 3 1.5
banana 5 2.0
cherry 2 1.8
如果我们想以空格为分隔符,打印每一行的第一个和第三个字段,可以使用以下命令:
awk '{print $1,$3}' data.txt
输出结果为:
apple 1.5
banana 2.0
cherry 1.8
如果文件中的字段是以冒号分隔,我们就需要使用-F
选项指定分隔符。比如有一个users.txt
文件,内容为:
user1:password1:1001
user2:password2:1002
user3:password3:1003
要打印每个用户的用户名(第一个字段)和用户 ID(第三个字段),命令如下:
awk -F: '{print $1,$3}' users.txt
输出:
user1 1001
user2 1002
user3 1003
注意:
单引号双引号都行,但是要按照规范
场景 | 单引号(') | 双引号(") | |
---|---|---|---|
纯正则匹配 | 推荐使用,语法简洁,避免 Shell 干扰 | 也可使用,但需注意转义 | |
包含 Shell 变量 | 无法直接使用,需额外处理 | 直接引用变量,Shell 先解析 | |
正则包含特殊字符 | 自动防止 Shell 解析,直接使用 | 需用反斜杠转义(如\$ 、\+ ) | |
避免引号嵌套错误 | 适合正则中无单引号的场景 | 适合正则中包含单引号的场景 |
1.1.2 脚本文件可执行方式
将所有 awk 命令插入一个文件,并使该文件可执行。同时,在脚本文件的首行使用#!/usr/bin/awk -f
,这样就可以通过直接键入脚本名称来调用它。
例如,创建一个名为process.awk
的文件,内容如下:
#!/bin/awk -f
{print $2}
然后为该文件添加可执行权限:
chmod +x process.awk
假设有一个input.txt
文件,内容为:
one two three
four five six
seven eight nine
执行process.awk
脚本处理input.txt
文件:
./process.awk input.txt
输出结果为:
two
five
eight
如果想要改变分隔符:
#!/bin/awk -f
# 设置字段分隔符为冒号
BEGIN { FS = ":" } #FS 的英文全称是Field Separator
{ print $1, $3 }
1.1.3 调用外部脚本方式
将所有的 awk 命令插入一个单独文件,然后使用-f
选项调用该脚本。语法为:
awk -f awk-script-file input-files
例如,有一个脚本文件calculate.awk
,内容如下:
{
sum = $1 + $2
print sum
}
我们有一个数据文件numbers.txt
,内容为:
3 5
2 7
4 6
执行命令:
awk -f calculate.awk numbers.txt
输出:
8
9
10
1.2 awk 脚本
1.2.1 模式和动作
任何 awk 语句都由模式和动作组成。模式部分决定动作语句何时触发及触发条件,而动作则是对数据进行的具体操作。如果省略模式部分,动作将对每一行输入数据都执行。
模式可以是条件语句、复合语句或正则表达式,其中有两个特殊字段BEGIN
和END
。BEGIN
语句用于设置计数、打印表头之类的操作,它会在任何文本浏览动作之前执行。END
语句则在 awk 完成对所有输入文本的浏览动作后执行,通常用于打印输出文本总数、结尾状态标志等。
动作通常放在大括号{}
内。动作最常见的是打印操作,但也可以包含更长的代码,如if
语句、循环语句及循环退出结构等。如果不指明动作,awk 将默认打印出所有输入记录。
例如,我们要处理一个成绩文件grades.txt
,内容如下:
Alice 85 90 78
Bob 70 65 80
Charlie 95 88 92
我们想在处理文件前打印表头,处理文件时打印每个学生的姓名和平均成绩,处理完后打印一个结束信息。可以使用以下 awk 脚本:
awk
BEGIN {
print "Student\tAverage Grade"
print "----------------------"
}
{
avg = ($2 + $3 + $4) / 3
print $1 "\t" avg
}
END {
print "----------------------"
print "End of Grades Report"
}
执行该脚本:
awk -f script.awk grades.txtsource files / command-line arguments must contain complete functions or rules
出现这个错误可能是你的大括号未闭合
输出:
Student Average Grade
----------------------
Alice 84.3333
Bob 71.6667
Charlie 91.6667
----------------------
End of Grades Report
1.2.2 域和记录
awk 执行时,会将输入的每一行视为一条记录,并且将记录中的各个部分(以分隔符分隔)标记为$1
、$2
……$n
,这种方式称为域标识。当需要指定多个域时,使用逗号,
进行分隔,如$1,$3
指定的是第一个域和第三个域。如果希望指定整行记录,则可以使用$0
。
使用print
命令执行打印操作,该命令需要用{}
括起来。
1.2.2.1 抽取域
以之前的grades.txt
文件为例,我们要打印每个学生的姓名(第一个域)和数学成绩(第二个域),命令如下:
awk '{print $1,$2}' grades.txt
输出:
Alice 85
Bob 70
Charlie 95
1.2.2.2 保存 awk 输出
保存 awk 输出结果主要有两种方式:
- 重定向到文件:这种方式下,屏幕不会显示输出内容,而是将结果保存到指定文件中。例如,将
grades.txt
文件中每个学生的姓名保存到students.txt
文件中,命令为:
awk '{print $1}' grades.txt > students.txt
- 使用管道将输出结果传给
tee
:tee
命令可以将输出同时显示在屏幕上并保存到文件中。例如,将grades.txt
文件中每个学生的总成绩(三个成绩之和)输出到屏幕并保存到total_grades.txt
文件中,命令如下:
awk '{sum=$2+$3+$4; print sum}' grades.txt | tee total_grades.txttee命令不可用的话,下载
yum install coreutils
1.2.2.3 使用标准输入
可以通过多种方法将标准输入作为 awk 的输入源:
- 直接在命令后跟上文件名,如
awk '{print $0}' grades.txt
,这里grades.txt
的内容会作为标准输入被 awk 读取处理。 - 使用
<
符号指定输入文件,如awk '{print $0}' < grades.txt
,效果与上一种方法相同。
1.2.2.4 打印所有记录
要打印输入文件的所有记录,使用以下命令:
awk '{print $0}' input_file
其中input_file
是要处理的文件名。
1.2.2.5 打印单独记录
只打印特定的域,如之前提到的只打印$1
和$4
:
awk '{print $1,$4}' input_file
1.2.2.6 自定义格式打印
我们可以在输出内容中添加注释、自定义分隔符等。例如,对于grades.txt
文件,我们要在输出学生姓名和平均成绩时,在上方添加注释 “Student Name” 和 “Average Score”,并使用制表符分隔,命令如下:
awk 'BEGIN {print "Student Name\tAverage Score\n------------------------------"} {avg = ($2 + $3 + $4) / 3; print $1 "\t" avg}' grades.txt
输出:
Student Name Average Score
------------------------------
Alice 84.3333
Bob 71.6667
Charlie 91.6667
1.2.2.7 awk 错误信息提示
在使用 awk 时,如果遇到错误,可以从以下几个方面排查:
- 确保整个 awk 命令用单引号括起来,因为双引号在 Shell 中可能会导致变量提前展开等问题,影响 awk 命令的执行。
- 确保命令内所有引号成对出现,无论是单引号还是双引号。
- 确保用大括号
{}
括起动作语句,用括号()
括起条件语句。 - 检查是否遗漏了大括号
{}
,尤其是在包含多个动作或复杂条件判断时。
1.2.2.8 awk 键盘输入
如果在执行 awk 命令时没有指定输入文件,awk 会从键盘读取输入。输入完成后,按Ctrl + D
组合键结束输入。例如,执行awk '{print $0}'
,然后在命令行输入一些文本,每输入一行按回车键,输入完成后按Ctrl + D
,awk 会打印出你输入的每一行内容。
1.2.3 元字符
awk 支持一些元字符,用于模式匹配等操作,常见的元字符有\
、^
、$
、.
、(
、)
、|
、*
、+
、?
。其中+
和?
在 grep 或 sed 中可能有不同的行为,但在 awk 中有其特定的含义:
+
:匹配一个或一个以上前面的单字符。例如,/XY+Z/
可以匹配XYZ
、XYYYYZ
等。?
:匹配 0 个或一个前面的单字符。例如,/XY?Z/
可以匹配XYZ
、XZ
。
例如,有一个文件strings.txt
,内容为:
XZY
XYZY
XYYYYZY
XZ
要匹配包含XY
后面跟着一个或多个Y
再跟着Z
的字符串,可以使用以下命令:
awk '/XY+Z/' strings.txt
输出:
XYZY
XYYYYZY
[root@free ~]# awk '/XY?Z/' string.txt
XZY
XYZY
XZ
1.2.4 条件操作符
awk 支持多种条件操作符,用于条件判断:
操作符 | 描述 |
---|---|
< | 小于 |
<= | 小于等于 |
== | 等于 |
!= | 不等于 |
> | 大于 |
>= | 大于等于 |
~ | 匹配正则表达式 |
!~ | 不匹配正则表达式 |
1.2.4.1 匹配
使用~
紧跟正则表达式可以匹配域,也可以使用if
语句进行条件判断,条件需要用()
括起来。
例如,有一个文件employees.txt
,内容如下:
John,Manager,50000
Alice,Engineer,45000
Bob,Engineer,48000
要查询职位是工程师(Engineer
)的员工信息(打印出$2
匹配Engineer
的行),可以使用以下两种方式:
awk '{if ($2~/Engineer/) print $0}' employees.txt为什么不需要分号?
因为 if 语句控制下的 print 是同一个逻辑块的一部分,不是独立的语句。正则表达式的基本语法:
在 awk 中,正则表达式通常用 /pattern/ 表示,其中:
开头的 /:标记正则表达式的开始;
结尾的 /:标记正则表达式的结束;
中间的 pattern:是具体的匹配模式(如 Engineer)。
或者
awk '$2 ~ /Engineer/' employees.txt
1.2.4.2 精确匹配
使用==
并用双引号括起条件可以进行精确匹配。例如,要查询薪资为48000
的员工信息:
awk '{if ($3 == "48000") print $0}' employees.txt
1.2.4.3 不匹配
使用!~
紧跟正则表达式可以实现不匹配域的操作。例如,要查询职位不是经理(Manager
)的员工信息:
awk '{if ($2!~/Manager/) print $0}' employees.txt
或者
awk '$2 !~ /Manager/' employees.txt
1.2.4.4 比较
以比较薪资大小为例,要找出薪资大于45000
的员工姓名和薪资:
awk '{if ($3 > 45000) print $1,$3}' employees.txt
1.2.4.5 各种匹配
- 匹配
Green
或green
:
awk '/(G|g)reen/' input_file
- 匹配
$1
的第四个字符是a
:
awk '$1 ~ /^...a/' input_file
- 匹配
Yellow
或Brown
:
awk '$4 ~ /Yellow|Brown/' input_file
- 匹配以
J
开头的行:
awk '$0 ~ /^J/' input_file
1.2.4.6 复合表达式
复合模式或复合操作符用于形成复杂的逻辑操作,复合表达式即为模式间通过使用复合操作符互相结合起来的表达式。常用的复合操作符有&&
(逻辑与)和||
(逻辑或):
&&
(AND):符号两边的条件必须同时为真。例如,要查询职位是工程师且薪资大于45000
的员工信息:
awk '{if ($2 == "Engineer" && $3 > 45000) print $0}' employees.txt
||
(OR):符号两边的条件只要有一个为真即可。例如,要查询职位是经理或者薪资大于48000
的员工信息:
awk '{if ($2 == "Manager" || $3 > 48000) print $0}' employees.txt
1.2.5 awk 内置变量
awk 有许多内置变量,用于设置环境信息、获取输入输出相关的状态等。以下是一些常用的内置变量:
ARGC
:表示命令行参数的个数。例如,执行awk -v var1=value1 -v var2=value2 -f script.awk file1 file2
,ARGC
的值为5
(包括awk
命令本身、两个-v
选项及两个文件名)。ARGV
:是一个数组,存储命令行参数的排列。ARGV[0]
通常是awk
命令本身,ARGV[1]
及后续元素为命令行参数。FNR
:与NR
类似,用于记录输入的行数。不同之处在于,FNR
在处理多个文件时,对每个文件都会从1
开始计数,而NR
是对所有输入文件的总行数进行计数。例如,有两个文件file1.txt
和file2.txt
,在处理file1.txt
时,FNR
和NR
从1
开始递增,当处理完file1.txt
开始处理file2.txt
时,NR
继续递增,而FNR
又从1
开始。FS
:用于指定输入字段的分隔符。可以在BEGIN
块中定义,也可以使用-F
选项在命令行指定。例如,BEGIN {FS=":"}
将输入字段分隔符设置为冒号。RS
:输入的记录分隔符,默认是换行符,即文本按一行一行输入。可以修改这个变量来改变记录的分隔方式。例如,BEGIN {RS="\n\n"}
可以将连续两个换行符作为记录分隔符,适用于处理段落等以空行分隔的文本。OFS
:输出字段分隔符,默认是空格。可以修改为其他字符,如制表符\t
或逗号,
等。例如,BEGIN {OFS="|"}
会将输出的字段用竖线分隔。ORS
:输出的记录分隔符,默认为换行符,即处理结果也是一行一行输出到屏幕。可以修改为其他字符,如BEGIN {ORS=","}
会将输出的记录用逗号分隔,最后一个记录后也会有逗号。
例如,我们要统计一个文件中每行的字段数,并输出行号、每行内容及字段数,使用以下脚本:
{
print NR, $0, NF
}
这里NR
表示行号,NF
表示当前行的字段数。假设文件example.txt
内容为:
apple banana cherry
dog cat mouse
执行awk '{print NR, $0, NF}' example.txt
,输出:
1 apple banana cherry 3
2 dog cat mouse 3
再如,我们想以冒号为输入分隔符,以逗号为输出分隔符,打印文件data.csv
的第一和第三个字段:
BEGIN {
FS=":"
OFS=","
}
{
print $1,$3
}
假设data.csv
内容为:
1:apple:red
2:banana:yellow
3:grape:purple
执行该脚本,输出:
1,red
2,yellow
3,purple