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 语句都由模式和动作组成。模式部分决定动作语句何时触发及触发条件,而动作则是对数据进行的具体操作。如果省略模式部分,动作将对每一行输入数据都执行。

模式可以是条件语句、复合语句或正则表达式,其中有两个特殊字段BEGINENDBEGIN语句用于设置计数、打印表头之类的操作,它会在任何文本浏览动作之前执行。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.txt


source 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

  • 使用管道将输出结果传给teetee命令可以将输出同时显示在屏幕上并保存到文件中。例如,将grades.txt文件中每个学生的总成绩(三个成绩之和)输出到屏幕并保存到total_grades.txt文件中,命令如下:

    awk '{sum=2+3+$4; print sum}' grades.txt | tee total_grades.txt

    tee命令不可用的话,下载
    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/可以匹配XYZXYYYYZ等。

  • ?:匹配 0 个或一个前面的单字符。例如,/XY?Z/可以匹配XYZXZ
    例如,有一个文件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 各种匹配
  • 匹配Greengreen

    awk '/(G|g)reen/' input_file

  • 匹配$1的第四个字符是a

    awk '$1 ~ /^...a/' input_file

  • 匹配YellowBrown

    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 file2ARGC的值为5(包括awk命令本身、两个-v选项及两个文件名)。

  • ARGV :是一个数组,存储命令行参数的排列。ARGV[0]通常是awk命令本身,ARGV[1]及后续元素为命令行参数。

  • FNR :与NR类似,用于记录输入的行数。不同之处在于,FNR在处理多个文件时,对每个文件都会从1开始计数,而NR是对所有输入文件的总行数进行计数。例如,有两个文件file1.txtfile2.txt,在处理file1.txt时,FNRNR1开始递增,当处理完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
相关推荐
FOREVER-Q3 分钟前
Windows 下通过 SSH 替代 Gitee OAuth Token 推送配置指南
运维·服务器
小兔崽子去哪了20 分钟前
Git 专题
git
金米kk32 分钟前
git pull时报错Your local changes to the following files would…的解决办法
git
学好statistics和DS33 分钟前
命令替换(Command Substitution)详解
linux
zimoyin33 分钟前
浅浅了解下0拷贝技术
java·linux·开发语言
超级罗伯特34 分钟前
git一次性完成仓库下载及所有分支获取
git·git仓库拉取
ba_pi1 小时前
每天写点什么2026-01-09-linux基础
linux·运维·服务器
BUTCHER51 小时前
Git 基础命令
git
少云清1 小时前
【性能测试】3_性能测试基础 _指标
运维·服务器·数据库·性能测试·性能测试指标
M malloc1 小时前
当你有两个git账号时,此时你gitpush冲突后如何解决push问题
git