awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大。简单来说awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理。
awk 命令和 sed 命令结构相同,通常情况下,awk 将每个输入行解释为一条记录而每一行中的内容(由空格或者制表符分隔)解释为每一个字段,一个或者多个连续空格或者制表符看做定界符。awk 中 $0
代表整个记录。
linux的awk命令
1 | awk ' /MA/ { print $1 }' list |
解释:打印包含 MA 的行中的第一个单词。再举一个具体的例子,比如
1 | echo 'this is one world\nthat is another world' | awk '{print $1}' |
那么输出就是 awk 处理之后的每一行第一个字符也就是
1 | this |
基本格式
awk 命令的基本格式
1 | awk [options] 'script' file |
options
这个表示一些可选的参数选项,script
表示 awk 的可执行脚本代码(一般被{}
花括号包围),这个是必须的。file
这个表示 awk 需要处理的文件,注意需要是纯文本文件(意味着 awk 能够处理)。
awk 自定义分隔符
之前提到的awk 默认的分割符为空格和制表符,awk 会根据这个默认的分隔符将每一行分为若干字段,依次用 $1
, $2
,$3
来表示,可以使用 -F
参数来指定分隔符
1 | awk -F ':' '{print $1}' /etc/passwd |
解释:使用 -F
来改变分隔符为 :
,比如上面的命令将 /etc/passwd 文件中的每一行用冒号 :
分割成多个字段,然后用 print 将第 1 列字段的内容打印输出
在 awk 中同时指定多个分隔符,比如现在有这样一个文件 some.log 文件内容如下
1 | Grape(100g)1980 |
现在我们想将上面的 some.log 文件中按照 “水果名称(重量)年份” 来进行分割
1 | $ awk -F '[()]' '{print $1, $2, $3}' some.log |
在 -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 命令工具
1 | peach 100 Mar 1997 China |
文件的每一行的每一列的内容除了可以用 print 命令打印输出以外,还可以对其进行赋值
1 | awk '{$2 = "***"; print $0}' fruit.txt |
上面的例子就是表示通过对 $2
变量进行重新赋值,来隐藏每一行的第 2 列内容,并且用星号 *
来代替其输出
在参数列表中加入一些字符串或者转义字符之类的东东
1 | awk '{print $1 "\t" $2 "\t" $3}' fruit.txt |
像上面这样,你可以在 print 的参数列表中加入一些字符串或者转义字符之类的东东,让输出的内容格式更漂亮,但一定要记住要使用双引号。
awk 内置 NR 变量表示每一行的行号
1 | awk '{print NR "\t" $0}' fruit.txt |
awk 内置 NF
变量表示每一行的列数
1 | awk '{print NF "\t" $0}' fruit.txt |
awk 中 $NF
变量的使用
awk '{print $NF}' fruit.txt
上面这个 $NF
就表示每一行的最后一列,因为 NF
表示一行的总列数,在这个文件里表示有 5 列,然后在其前面加上 $
符号,就变成了 $5
,表示第 5 列
1 | awk '{print $(NF - 1)}' fruit.txt |
上面 $(NF-1)
表示倒数第 2 列, $(NF-2)
表示倒数第 3 列,依次类推。
1 | awk 'NR % 6' # 打印出了 6 倍数行之外的其他行 |
awk 内置函数
awk 还提供了一些内置函数,比如
toupper()
用于将字符转为大写tolower()
将字符转为小写length()
长度substr()
子字符串sin()
正弦cos()
余弦sqrt()
平方根rand()
随机数
更多的方法可以参考 man awk
awk 同时处理多个文件
1 | awk '{print FILENAME "\t" $0}' demo1.txt demo2.txt |
当你使用 awk 同时处理多个文件的时候,它会将多个文件合并处理,变量FILENAME
就表示当前文本行所在的文件名称。
BEGIN 关键字的使用
在脚本代码段前面使用 BEGIN 关键字时,它会在开始读取一个文件之前,运行一次 BEGIN
关键字后面的脚本代码段, BEGIN 后面的脚本代码段只会执行一次,执行完之后 awk 程序就会退出
1 | awk 'BEGIN {print "Start read file"}' /etc/passwd |
awk 脚本中可以用多个花括号来执行多个脚本代码,就像下面这样
1 | 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 脚本中声明和使用变量
1 | awk '{msg="hello world"; print msg}' /etc/passwd |
awk 声明的变量可以在任何多个花括号脚本中使用
1 | awk 'BEGIN {msg="hello world"} {print msg}' /etc/passwd |
在 awk 中使用数学运算,在 awk 中,像其他编程语言一样,它也支持一些基本的数学运算操作
1 | awk '{a = 12; b = 24; print a + b}' company.txt |
上面这段脚本表示,先声明两个变量 a = 12 和 b = 24,然后用 print 打印出 a 加上 b 的结果。
请记住 awk 是针对文件的每一行来执行一次单引号 里面的脚本代码,每读取到一行就会执行一次,文件里面有多少行就会执行多少次,但 BEGIN 和 END 关键字后脚本代码除外,如果被处理的文件中什么都没有,那 awk 就一次都不会执行。
awk 还支持其他的数学运算符
1 | + 加法运算符 |
在 awk 中使用条件判断
比如有一个文件 company.txt 内容如下
yahoo 100 4500
google 150 7500
apple 180 8000
twitter 120 5000
如果要判断文件的第 3 列数据,也就是平均工资小于 5500 的公司,然后将其打印输出
1 | awk '$3 < 5500 {print $0}' company.txt |
上面的命令结果就是平均工资小于 5500 的公司名单,$3 < 5500
表示当第 3 列字段的内容小于 5500 的时候才会执行后面的 {print $0} 代码块
1 | awk '$1 == "yahoo" {print $0}' company.txt |
awk 还有一些其他的条件操作符如下
运算符 | 描述 |
---|---|
< | 小于 |
<= | 小于或等于 |
== | 等于 |
!= | 不等于 |
> | 大于 |
>= | 大于或等于 |
~ | 匹配正则表达式 |
!~ | 不匹配正则表达式 |
使用 if 指令判断来实现上面同样的效果
1 | awk '{if ($3 < 5500) print $0}' company.txt |
上面表示如果第 3 列字段小于 5500 的时候就会执行后面的 print $0
在 awk 中使用正则表达式
比如现在我们有这么一个文件 poetry.txt 内容如下:
1 | This above all: to thine self be true |
使用正则表达式匹配字符串 “There” ,将包含这个字符串的行打印并输出
1 | awk '/There/{print $0}' poetry.txt |
使用正则表达式配一个包含字母 t 和字母 e ,并且 t 和 e 中间只能有任意单个字符的行
1 | awk '/t.e/{print $0}' poetry.txt |
如果只想匹配单纯的字符串 “t.e”, 那正则表达式就是这样的 /t.e/ ,用反斜杠来转义 . 符号 因为 . 在正则表达式里面表示任意单个字符。
使用正则表达式来匹配所有以 “The” 字符串开头的行
1 | awk '/^The/{print $0}' poetry.txt |
在正则表达式中 ^
表示以某某字符或者字符串开头。
使用正则表达式来匹配所有以 “true” 字符串结尾的行
1 | awk '/true$/{print $0}' poetry.txt |
在正则表达式中 $ 表示以某某字符或者字符串结尾。
1 | awk '/m[a]t/{print $0}' poetry.txt |
上面这个正则表达式 /m[a]t/
表示匹配包含字符 m ,然后接着后面包含中间方括号中表示的单个字符 a ,最后包含字符 t 的行,输出结果中只有单词 “matter” 符合这个正则表达式的匹配。因为正则表达式 [a] 方括号中表示匹配里面的任意单个字符。
继续上面的一个新例子如下
1 | awk '/^Th[ie]/{print $0}' poetry.txt |
这个例子中的正则表达式 /^Th[ie]/ 表示匹配以字符串 “Thi” 或者 “The” 开头的行,正则表达式方括号中表示匹配其中的任意单个字符。
再继续上面的新的用法
1 | 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 过滤 history 输出,找到最常用的命令1
history | awk '{a[$2]++}END{for(i in a){print a[i] " " i}}' | sort -rn | head
过滤文件中重复行1
awk '!x[$0]++' <file>
将一行长度超过 72 字符的行打印1
awk 'length>72' file
查看最近哪些用户使用系统1
last | grep -v "^$" | awk '{ print $1 }' | sort -nr | uniq -c
假设有一个文本,每一行都是一个 int 数值,想要计算这个文件每一行的和,可以使用1
awk '{s+=$1} ENG {printf "%.0f", s}' /path/to/file