
awk文字处理小笔记(上)
awk文本处理器
概念
awk是一种用于处理文本和数据的编程语言。名称来自三位创建者Alfred Aho、Peter Weinberger和Brian Kernighan的姓氏首字母。
- 可以处理标准输入、一个或多个文件,以及其他命令的输出
- 支持变量、条件判断、循环、数组和函数
- 既可以直接写在命令行中,也可以保存为脚本使用
如何理解
如果说sed擅长“按行修改”,那么awk很适合“把每行分成几列,再按条件处理”。
常用文本工具对应的使用场景
grep:搜索文本,输出匹配的行sed:对文本进行替换、删除、插入等编辑awk:按字段提取内容,进行判断、统计和格式化输出
原理
同一条记录可以匹配多条规则,awk会按书写顺序逐条判断。默认会检查所有普通规则;使用next时,会跳过当前记录剩余的规则,直接读取下一条记录。
默认使用换行符分隔记录,因此可以先把“记录”理解为一行,把“字段”理解为一列。
- 没有指定模式时,对每条记录执行动作
- 普通模式没有指定动作时,默认执行
print $0 BEGIN和END是特殊模式,必须有动作块,不能单独写成一个裸模式
使用说明
基本语法:awk [选项] '模式 {动作}' [文件名 ...]
选项
| 选项 | 说明 |
|---|---|
-F 分隔符 |
指定输入字段分隔符 |
-v 变量=值 |
在程序执行前设置变量,可重复使用 |
-f 脚本文件 |
从文件读取awk程序 |
脚本可以写成:'BEGIN {初始化} 模式 {处理} END {收尾}'。
三个部分按需要选用,不必同时存在。程序通常放在单引号内,避免Shell提前展开$1等内容。
内置变量
| 变量 | 说明 |
|---|---|
$0 |
当前整条记录 |
$n |
当前记录的第n个字段,例如$1 |
NF |
当前记录的字段数量 |
$NF |
当前记录的最后一个字段 |
NR |
从所有输入文件累计读取的记录数 |
FNR |
当前文件已读取的记录数,每个新文件从1开始 |
FILENAME |
当前正在处理的文件名 |
FS |
输入字段分隔符,默认值是一个空格,具有特殊分割规则 |
OFS |
print多个参数之间的分隔符,默认是一个空格 |
RS |
输入记录分隔符,默认是换行符\n |
ORS |
print每次输出末尾的分隔符,默认是换行符\n |
ARGC、ARGV |
命令行参数数量和参数数组 |
ENVIRON |
环境变量组成的关联数组 |
CONVFMT |
数值转为字符串时的格式,默认%.6g |
OFMT |
print输出数值时使用的格式,默认%.6g |
RSTART、RLENGTH |
match()匹配的位置和长度 |
SUBSEP |
多维数组组合下标时使用的分隔符,默认"\034" |
GNU awk扩展变量
| 变量 | 说明 |
|---|---|
ARGIND |
当前文件在ARGV中的下标;ARGV[0]通常为程序名称 |
ERRNO |
某些输入输出错误的描述 |
FIELDWIDTHS |
按固定宽度分割字段的宽度列表 |
IGNORECASE |
控制大小写是否参与字符串和正则比较 |
💡提示
awk可能是GNU awk、mawk,也可能是BSD awk。上面的扩展变量不能假定所有实现都支持,本文主体示例使用常见的通用语法。
内置函数
| 函数 | 说明 |
|---|---|
rand() |
返回大于等于0、小于1的伪随机数 |
srand([种子]) |
设置随机种子,不传参数时通常使用当前时间 |
int(x) |
向0截断小数部分,例如int(-2.8)为-2 |
sqrt(x) |
平方根 |
sin(x)、cos(x) |
正弦、余弦,角度使用弧度 |
toupper(s)、tolower(s) |
字符串转为大写、小写 |
length(s) |
字符串长度;省略参数时使用$0 |
substr(s, 起点, 长度) |
提取子串,起点从1开始,长度可以省略 |
index(s, 子串) |
返回子串首次出现的位置,未找到时返回0 |
sub()、gsub() |
替换第一个匹配、替换所有匹配 |
split(s, 数组, 分隔符) |
将字符串拆分到数组,返回字段数 |
基本用法
1.基础打印
先不使用选项,也不指定模式,只写最简单的动作。
普通打印
1 | echo aaa | awk '{print}' |
1 | aaa |
{print}与{print $0}效果相同,都输出当前整条记录。
单列打印
1 | echo 'this is a test' | awk '{print $3}' |
1 | a |
$3表示第3列。默认的FS=" "会将连续的空格和制表符作为分隔,并忽略记录前后的空格和制表符,并不是每个空格都分出一个字段。
NF和$NF
1 | echo 'this is a test' | awk '{print NF; print $NF}' |
1 | 4 |
NF表示有多少个字段$NF表示第NF个字段,也就是最后一列
一个回答“有几列”,一个回答“最后一列是什么”。只差一个$,工作内容可没少差。
多列打印
1 | echo 'this is a test' | awk '{print $1,$3}' |
1 | this a |
用逗号分隔多个输出参数,awk会在它们之间插入输出字段分隔符OFS。
组合打印
1 | echo 'this is a test' | awk '{print "Hello," $NF}' |
1 | Hello,test |
字符串和变量相邻时表示拼接,不会自动插入空格。如果想显示Hello, test,可以写成print "Hello,", $NF。
💡提示
$1外面不要加双引号。print "$1"输出的是字面文本$1。
2.特殊模式
BEGIN
读取文本之前执行的操作。
1 | awk 'BEGIN {print "Hello"}' |
1 | Hello |
只有BEGIN动作时,不需要提供输入文件。
如果想先执行BEGIN,再逐行处理文本,可以这样写。
1 | echo 'this is a test' | awk 'BEGIN {print "Hello"} {print $0}' |
1 | Hello |
不要在上述管道命令后再随意加一个test文件名:指定文件后,正常读取的是文件,管道中的内容不会自动与文件内容合并。
END
输入处理结束后执行的操作。
1 | echo 'this is a test' | awk '{print $0} END {print "Hello"}' |
1 | this is a test |
扩展:两种模式结合使用
1 | echo 'this is a test' | awk 'BEGIN {print "Now"} {print $0} END {print "Hello"}' |
1 | Now |
3.分隔符
输入字段分隔符
通过-F指定分隔符,把每条记录切成多个字段。
例如,一份简单的服务清单,按服务名#环境#位置#端口排列,我们只想取服务名和端口。
1 | printf '%s\n' 'blog#dev#local#3000' 'api#test#local#8080' | awk -F '#' '{print $1,$4}' |
1 | blog 3000 |
也可以使用-v设置内置变量FS。
1 | printf '%s\n' 'blog#dev#local#3000' 'api#test#local#8080' | awk -v FS='#' '{print $1,$4}' |
FS还可以在BEGIN中设置,不是必须通过-v赋值。例如:awk 'BEGIN {FS="#"} {print $1,$4}'。
输出字段分隔符
1 | echo 'this is a test' | awk -v OFS='#' '{print $1,$4}' |
1 | this#test |
输入和输出分隔符结合使用
1 | printf '%s\n' 'blog#dev#local#3000' 'api#test#local#8080' | awk -v FS='#' -v OFS='_' '{print $1,$4}' |
1 | blog_3000 |
补充:连接在一起输出
1 | echo 'this is a test' | awk '{print $1,$4; print $1 $4}' |
1 | this test |
有逗号时使用OFS分隔;没有逗号、表达式相邻时表示拼接。
4.变量
内置变量举例
NR和FNR
1 | printf '%s\n' 'apple red' 'banana yellow' > a.txt |
1 | a.txt 1 1 apple |
NR跨文件累加,FNR在新文件中重新计数。
RS:输入记录分隔符
1 | printf '%s' 'this is a test' | awk -v RS=' ' '{print $0}' |
1 | this |
这里每遇到一个空格,就结束一条记录。RS分割记录,FS分割记录内部的字段。
ORS:输出记录分隔符
1 | printf '%s\n' 'aaa' 'bbb' | awk -v ORS='++' '{print $0}' |
1 | aaa++bbb++ |
print每次输出末尾都会添加ORS。这里没有结尾换行,终端提示符可能会紧接在++后面。
自定义变量
方法一:使用-v
1 | echo 'this is a test' | awk -v name='Shell' '{print name,$NF}' |
1 | Shell test |
方法二:在程序中赋值
1 | echo 'this is a test' | awk 'BEGIN {name="Shell"} {print name,$NF}' |
变量名区分大小写,不需要像Shell那样用$name取值。同一代码块内的多条语句,可以用分号或换行隔开。
5.模式(Pattern)
模式相当于条件:满足条件的记录才执行后面的动作。
关系表达式
1 | printf '%s\n' '3 2' '2 2' '1 2' > numbers.txt |
1 | 3 2 |
上面的三行输出分别对应三个命令。
输出奇数行
1 | awk 'NR % 2 == 1 {print NR,$1}' numbers.txt |
1 | 1 3 |
输出第6行以后的行
1 | awk 'NR > 6 {print $1}' /etc/os-release |
/etc/os-release属于Linux系统文件,内容随发行版变化。
| 运算符 | 含义 | 示例 |
|---|---|---|
<、<= |
小于、小于等于 | $1 < 10 |
==、!= |
等于、不等于 | $1 == "root" |
>、>= |
大于、大于等于 | $3 >= 1000 |
~ |
匹配正则 | $1 ~ /^root/ |
!~ |
不匹配正则 | $1 !~ /^root/ |
&& |
两个条件同时满足 | NR >= 5 && NR <= 10 |
💡提示
==是比较,=是赋值。筛选第5行应该写NR == 5,不要写成NR = 5。
正则模式
awk使用扩展正则表达式,正则常量写在两个斜线之间。
1 | awk '/^root/ {print $0}' /etc/passwd |
对应的grep写法是:grep '^root' /etc/passwd。
没有写动作时,默认输出整条记录,因此可以简写为:
1 | awk '/^root/' /etc/passwd |
如果只想精确匹配用户名root,按字段判断更清楚。
1 | awk -F ':' '$1 == "root" {print}' /etc/passwd |
补充
现代GNU awk默认支持
{m,n}次数匹配,不需要额外添加--posix或--re-interval。较旧的awk实现可能存在差异。
小结
(1)awk默认按行读取、按空白分割字段
(2)$0是整条记录,$1是第1列,NF是字段数,$NF是最后一列
(3)BEGIN在读取前执行,END在输入处理结束后执行
(4)FS和OFS处理字段分隔,RS和ORS处理记录分隔








