linux的awk命令

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
2
this
that

基本格式

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
2
3
4
5
Grape(100g)1980
raisins(500g)1990
plum(240g)1997
apricot(180g)2005
nectarine(200g)2008

现在我们想将上面的 some.log 文件中按照 “水果名称(重量)年份” 来进行分割

1
2
3
4
5
6
$ 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 命令工具

1
2
3
4
5
6
7
8
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 命令打印输出以外,还可以对其进行赋值

1
awk '{$2 = "***"; print $0}' fruit.txt

上面的例子就是表示通过对 $2 变量进行重新赋值,来隐藏每一行的第 2 列内容,并且用星号 * 来代替其输出

在参数列表中加入一些字符串或者转义字符之类的东东

1
awk '{print $1 "\t" $2 "\t" $3}' fruit.txt

像上面这样,你可以在 print 的参数列表中加入一些字符串或者转义字符之类的东东,让输出的内容格式更漂亮,但一定要记住要使用双引号。

awk 内置 NR 变量表示每一行的行号

1
2
3
4
5
6
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 变量表示每一行的列数

1
2
3
4
5
6
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 列

1
2
3
4
5
6
awk '{print $(NF - 1)}' fruit.txt

1997
1986
1990
2008

上面 $(NF-1) 表示倒数第 2 列, $(NF-2) 表示倒数第 3 列,依次类推。

1
2
3
4
5
6
7
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 同时处理多个文件

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
2
3
4
5
+ 加法运算符
- 减法运算符
* 乘法运算符
/ 除法运算符
% 取余运算符

在 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
2
3
4
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” ,将包含这个字符串的行打印并输出

1
2
3
4
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 中间只能有任意单个字符的行

1
2
3
4
5
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” 字符串开头的行

1
awk '/^The/{print $0}' poetry.txt

在正则表达式中 ^表示以某某字符或者字符串开头。

使用正则表达式来匹配所有以 “true” 字符串结尾的行

1
awk '/true$/{print $0}' poetry.txt

在正则表达式中 $ 表示以某某字符或者字符串结尾。

1
2
3
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] 方括号中表示匹配里面的任意单个字符。

继续上面的一个新例子如下

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

reference