awk 是一个强大的文本分析工具,它是 Linux 中功能强大的数据处理引擎之一,awk 可以非常轻松地处理比如每行都是相同格式的文本,比如日志,csv 格式等等。相对于 grep 的查找,sed 的编辑,awk 在其对数据分析并生成报告时,显得尤为强大。
当使用 awk 命令处理一个或者多个文件时,它会依次读取文件的每一行内容,然后对其进行处理,awk 命令默认从 stdio 标准输入获取文件内容,awk 使用一对单引号来表示一些可执行的脚本代码,在可执行脚本代码里面,使用一对花括号来表示一段可执行代码块,可以同时存在多个代码块。awk 的每个花括号内同时可以有多个指令,每一个指令用分号分隔,awk 其实就是一个脚本编程语言。
awk 命令和 sed 命令结构相同,通常情况下,awk 将每个输入行解释为一条记录而每一行中的内容(由空格或者制表符分隔)解释为每一个字段,一个或者多个连续空格或者制表符看做定界符。awk 中 $0
代表整个记录。
awk ' /MA/ { print $1 }' list
解释:打印包含 MA 的行中的第一个单词。再举一个具体的例子,比如
echo 'this is one world\nthat is another world' | awk '{print $1}'
那么输出就是 awk 处理之后的每一行第一个字符也就是:
this
that
基本格式
awk 命令的基本格式
awk [options] 'script' file
options
这个表示一些可选的参数选项,script
表示 awk 的可执行脚本代码(一般被{}
花括号包围),这个是必须的。file
这个表示 awk 需要处理的文件,注意需要是纯文本文件(意味着 awk 能够处理)。
awk 自定义分隔符
之前提到的,awk 默认的分割符为空格和制表符,awk 会根据这个默认的分隔符将每一行分为若干字段,依次用 $1
, $2
,$3
来表示,可以使用 -F
参数来指定分隔符
awk -F ':' '{print $1}' /etc/passwd
解释:使用 -F
来改变分隔符为 :
,比如上面的命令将 /etc/passwd
文件中的每一行用冒号 :
分割成多个字段,然后用 print 将第 1 列字段的内容打印输出
在 awk 中同时指定多个分隔符,比如现在有这样一个文件 some.log 文件内容如下
Grape(100g)1980
raisins(500g)1990
plum(240g)1997
apricot(180g)2005
nectarine(200g)2008
现在我们想将上面的 some.log 文件中按照 “水果名称(重量)年份” 来进行分割
$ awk -F '[()]' '{print $1, $2, $3}' some.log
Grape 100g 1980
raisins 500g 1990
plum 240g 1997
apricot 180g 2005
nectarine 200g 2008
在 -F
参数中使用一对方括号来指定多个分隔符,awk 处理 some.log 文件时就会使用 “(“ 或者 “)” 来对文件的每一行进行分割。
awk 内置变量的使用
awk 除了 $
和数字表示字段还有一些其他的内置变量:
$0
这个表示文本处理时的当前行,$1
表示文本行被分隔后的第 1 个字段列,$2
表示文本行被分割后的第 2 个字段列,$3
表示文本行被分割后的第 3 个字段列,$n 表示文本行被分割后的第 n 个字段列- NR 表示文件中的行号,表示当前是第几行
- NF 表示文件中的当前行被分割的列数,可以理解为 MySQL 数据表里面每一条记录有多少个字段,所以
$NF
就表示最后一个字段,$(NF-1)
就表示倒数第二个字段 - FS 表示 awk 的输入分隔符,默认分隔符为空格和制表符,可以对其进行自定义设置
- OFS 表示 awk 的输出分隔符,默认为空格,也可以对其进行自定义设置
- FILENAME 表示当前文件的文件名称,如果同时处理多个文件,它也表示当前文件名称
- RS 行分隔符,用于分割行,默认为换行符
- ORS 输出记录的分隔符,默认为换行符
比如我们有这么一个文本文件 fruit.txt 内容如下,用它来演示如何使用 awk 命令工具
peach 100 Mar 1997 China
Lemon 150 Jan 1986 America
Pear 240 Mar 1990 Janpan
avocado 120 Feb 2008 china
awk '{print $0}' fruit.txt # 表示打印输出文件的每一整行的内容
awk '{print $1}' fruit.txt # 表示打印输出文件的每一行的第 1 列内容
awk '{print $1, $2}' fruit.txt
文件的每一行的每一列的内容除了可以用 print 命令打印输出以外,还可以对其进行赋值
awk '{$2 = "***"; print $0}' fruit.txt
上面的例子就是表示通过对 $2
变量进行重新赋值,来隐藏每一行的第 2 列内容,并且用星号 *
来代替其输出
在参数列表中加入一些字符串或者转义字符之类的东东
awk '{print $1 "\t" $2 "\t" $3}' fruit.txt
像上面这样,你可以在 print 的参数列表中加入一些字符串或者转义字符之类的东东,让输出的内容格式更漂亮,但一定要记住要使用双引号。
awk 内置 NR 变量表示每一行的行号
awk '{print NR "\t" $0}' fruit.txt
1 peach 100 Mar 1997 China
2 Lemon 150 Jan 1986 America
3 Pear 240 Mar 1990 Janpan
4 avocado 120 Feb 2008 china
awk 内置 NF 变量表示每一行的列数
awk '{print NF "\t" $0}' fruit.txt
5 peach 100 Mar 1997 China
5 Lemon 150 Jan 1986 America
5 Pear 240 Mar 1990 Janpan
5 avocado 120 Feb 2008 china
awk 中 $NF 变量的使用
awk '{print $NF}' fruit.txt
上面这个 $NF
就表示每一行的最后一列,因为 NF 表示一行的总列数,在这个文件里表示有 5 列,然后在其前面加上 $
符号,就变成了 $5
,表示第 5 列
awk '{print $(NF - 1)}' fruit.txt
1997
1986
1990
2008
上面 $(NF-1) 表示倒数第 2 列, $(NF-2) 表示倒数第 3 列,依次类推。
awk 'NR % 6' # 打印出了 6 倍数行之外的其他行
awk 'NR > 5' # 打印第 5 行之后内容,类似 `tail -n +6` 或者 `sed '1,5d'`
awk 'NF >= 6' # 打印大于等于 6 列的行
awk '/foo/ && /bar/' # 打印匹配 `/foo/` 和 `/bar/` 的行
awk '/foo/ && !/bar/' # 打印包含 `/foo/` 不包含 `/bar/` 的行
awk '/foo/ || /bar/' # 或
awk '/foo/,/bar/' # 打印从匹配 `/foo/` 开始的行到 `/bar/` 的行,包含这两行
awk 内置函数
awk 还提供了一些内置函数,比如:
toupper()
用于将字符转为大写tolower()
将字符转为小写length()
长度substr()
子字符串sin()
正弦cos()
余弦sqrt()
平方根rand()
随机数
更多的方法可以参考:man awk
awk 同时处理多个文件
awk '{print FILENAME "\t" $0}' demo1.txt demo2.txt
当你使用 awk 同时处理多个文件的时候,它会将多个文件合并处理,变量 FILENAME 就表示当前文本行所在的文件名称。
BEGIN 关键字的使用
在脚本代码段前面使用 BEGIN 关键字时,它会在开始读取一个文件之前,运行一次 BEGIN 关键字后面的脚本代码段, BEGIN 后面的脚本代码段只会执行一次,执行完之后 awk 程序就会退出
awk 'BEGIN {print "Start read file"}' /etc/passwd
awk 脚本中可以用多个花括号来执行多个脚本代码,就像下面这样
awk 'BEGIN {print "Start read file"} {print $0}' /etc/passwd
END 关键字使用方法
awk 的 END 指令和 BEGIN 恰好相反,在 awk 读取并且处理完文件的所有内容行之后,才会执行 END 后面的脚本代码段
awk 'END {print "End file"}' /etc/passwd
awk 'BEGIN {print "Start read file"} {print $0} END {print "End file"}' /etc/passwd
在 awk 中使用变量
可以在 awk 脚本中声明和使用变量
awk '{msg="hello world"; print msg}' /etc/passwd
awk 声明的变量可以在任何多个花括号脚本中使用
awk 'BEGIN {msg="hello world"} {print msg}' /etc/passwd
在 awk 中使用数学运算,在 awk 中,像其他编程语言一样,它也支持一些基本的数学运算操作
awk '{a = 12; b = 24; print a + b}' company.txt
上面这段脚本表示,先声明两个变量 a = 12 和 b = 24,然后用 print 打印出 a 加上 b 的结果。
请记住 awk 是针对文件的每一行来执行一次单引号 里面的脚本代码,每读取到一行就会执行一次,文件里面有多少行就会执行多少次,但 BEGIN 和 END 关键字后脚本代码除外,如果被处理的文件中什么都没有,那 awk 就一次都不会执行。
awk 还支持其他的数学运算符
+ 加法运算符
- 减法运算符
* 乘法运算符
/ 除法运算符
% 取余运算符
在 awk 中使用条件判断
比如有一个文件 company.txt 内容如下
yahoo 100 4500
google 150 7500
apple 180 8000
twitter 120 5000
如果要判断文件的第 3 列数据,也就是平均工资小于 5500 的公司,然后将其打印输出
awk '$3 < 5500 {print $0}' company.txt
上面的命令结果就是平均工资小于 5500 的公司名单,$3 < 5500
表示当第 3 列字段的内容小于 5500 的时候才会执行后面的 {print $0} 代码块
awk '$1 == "yahoo" {print $0}' company.txt
awk 还有一些其他的条件操作符如下
< 小于
<= 小于或等于
== 等于
!= 不等于
> 大于
>= 大于或等于
~ 匹配正则表达式
!~ 不匹配正则表达式
使用 if 指令判断来实现上面同样的效果
awk '{if ($3 < 5500) print $0}' company.txt
上面表示如果第 3 列字段小于 5500 的时候就会执行后面的 print $0
在 awk 中使用正则表达式
比如现在我们有这么一个文件 poetry.txt 内容如下:
This above all: to thine self be true
There is nothing either good or bad, but thinking makes it so
There’s a special providence in the fall of a sparrow
No matter how dark long, may eventually in the day arrival
使用正则表达式匹配字符串 “There” ,将包含这个字符串的行打印并输出
awk '/There/{print $0}' poetry.txt
There is nothing either good or bad, but thinking makes it so
There’s a special providence in the fall of a sparrow
使用正则表达式配一个包含字母 t 和字母 e ,并且 t 和 e 中间只能有任意单个字符的行
awk '/t.e/{print $0}' poetry.txt
There is nothing either good or bad, but thinking makes it so
There’s a special providence in the fall of a sparrow
No matter how dark long, may eventually in the day arrival
如果只想匹配单纯的字符串 “t.e”, 那正则表达式就是这样的 /t.e/ ,用反斜杠来转义 . 符号 因为 . 在正则表达式里面表示任意单个字符。
使用正则表达式来匹配所有以 “The” 字符串开头的行
awk '/^The/{print $0}' poetry.txt
在正则表达式中 ^ 表示以某某字符或者字符串开头。
使用正则表达式来匹配所有以 “true” 字符串结尾的行
awk '/true$/{print $0}' poetry.txt
在正则表达式中 $ 表示以某某字符或者字符串结尾。
awk '/m[a]t/{print $0}' poetry.txt
No matter how dark long, may eventually in the day arrival
上面这个正则表达式 /m[a]t/
表示匹配包含字符 m ,然后接着后面包含中间方括号中表示的单个字符 a ,最后包含字符 t 的行,输出结果中只有单词 “matter” 符合这个正则表达式的匹配。因为正则表达式 [a] 方括号中表示匹配里面的任意单个字符。
继续上面的一个新例子如下
awk '/^Th[ie]/{print $0}' poetry.txt
这个例子中的正则表达式 /^Th[ie]/ 表示匹配以字符串 “Thi” 或者 “The” 开头的行,正则表达式方括号中表示匹配其中的任意单个字符。
再继续上面的新的用法
awk '/s[a-z]/{print $0}' poetry.txt
正则表达式 /s[a-z]/
表示匹配包含字符 s 然后后面跟着任意 a 到 z 之间的单个字符的字符串,比如 “se”, “so”, “sp” 等等。
正则表达式 [] 方括号中还有一些其他用法比如下面这些
[a-zA-Z] 表示匹配小写的 a 到 z 之间的单个字符,或者大写的 A 到 Z 之间的单个字符
[^a-z] 符号 `^` 在方括号里面表示取反,也就是非的意思,表示匹配任何非 a 到 z 之间的单个字符
正则表达式中的星号 *
和加号 +
的使用方法,*
表示匹配星号前字符串 0 次或者多次,+
和星号原理差不多,只是加号表示任意 1 个或者 1 个以上,也就是必须至少要出现一次。
正则表达式问号 ? 的使用方法,正则中的问号 ?
表示它前面的字符只能出现 0 次 或者 1 次。
正则表达式中的 {} 花括号用法,花括号 {} 表示规定它前面的字符必须出现的次数,像这个 /go{2}d/ 就表示只匹配字符串 “good”,也就是中间的字母 “o” 必须要出现 2 次。
正则表达式中的花括号还有一些其他的用法如下
/go{2,10}d/ 表示字母 "o" 只能可以出现 2 次,3 次,4 次,5 次,6 次 ... 一直到 10 次
/go{2,}d/ 表示字母 "o" 必须至少出现 2 次或着 2 次以上
正则表达式中的圆括号表示将多个字符当成一个完整的对象来看待。比如 /th(in){1}king/ 就表示其中字符串 “in” 必须出现 1 次。而如果不加圆括号就变成了 /thin{1}king/ 这个就表示其中字符 “n” 必须出现 1 次。
使用 AWK 移除行中特定模式
接上面正则使用,比如文件中有行数据
/abc/def/123 456
/abc/def/222 456
想要移除 123,保留之前的字母和后面的数字,则可以使用
awk 'sub(/[0-9]+/,"",$1)' /path/to/file
一些组合使用
使用 awk 过滤 history 输出,找到最常用的命令
history | awk '{a[$2]++}END{for(i in a){print a[i] " " i}}' | sort -rn | head
过滤文件中重复行
awk '!x[$0]++' <file>
将一行长度超过 72 字符的行打印
awk 'length>72' file
查看最近哪些用户使用系统
last | grep -v "^$" | awk '{ print $1 }' | sort -nr | uniq -c
假设有一个文本,每一行都是一个 int 数值,想要计算这个文件每一行的和,可以使用
awk '{s+=$1} ENG {printf "%.0f", s}' /path/to/file
reference
- http://www.ruanyifeng.com/blog/2018/11/awk.html