blueyi's notes

Follow Excellence,Success will chase you!

0%

Linux实用工具总结之awk

一些会比较经常用到的Linux下的工具,当然都是命令行。多数工具需要与正则表达式配合使用,所以可以很多工具使用前都需要先了解正则表达式,正则表达式可以参考这里正则表达式学习笔记

关于

awk其实并不算一个工具,相当于是一个脚本语言,awk就是这个脚本语言的解释器,这是GNU手册中的定义:pattern scanning and processing language。本文就把它当成一个简单的文本流处理工具来用,虽然我们有了sed,但sed在处理一行中的各列(某种分隔符进行分隔的字符串)时远没有awk功能强大。awk也是以某种模式一行行地处理各列,但对于各列中的内容可以非常精确化地处理。awk会将所处理行中的各列分别赋给$1..$n,$0表示当前一整行。
awk的用法为:awk [可选项] '脚本' [INPUTFILES],或者处理awk脚本文件:awk [可选项] -f SCRIPTFILE [INPUTFILES],当然awk也可以直接用于处理管道传递来的文本流
下面分别说一下这几部分:

可选项

awk的可选项非常多,这里只说几个常用的

  • -F[fs] 指定列项分隔符fs,fs可以是一个字符串或者表达式,如-F:表示以:分隔的各列,默认情况下分隔符是空格或tab
  • -f SCRIPTFILE 从脚本文件中读取awk命令
  • -v var=value 自定义一个变量并赋值

脚本语句

awk的脚本语句格式通常为:'[条件判断1][命令1] [条件判断2][命令2]...'
脚本需要被放在单引号中,条件类型和命令都是可选的,当只有条件判断而没有命令时,则会输出符合条件的整行。当然如果只有命令而没有条件判断时,则对每一行按命令处理。命令及命令参数需要使用一对大括号括起来,最常用的命令就是print和printf,这两个命令其实都是linux内建的命令,prinf用于输出变量,类似与echo,默认在结尾输出换行。printf类似于C语言中的格式化输出函数printf,可以根据格式化输出参数来格式化输出,默认没有换行,所以如果需要换行就需要使用\n,不需要使用小括号,直接将需要。
先来举几个个简单的例子:
此处都是/etc/passwd文件为例
输出/etc/passwd中的第三列小于10的行:
awk -F: '$3<10' /etc/passwd
这里我们通过-F指定分隔符为:,因为/etc/passwd中的各行包含的内容都以:分隔,然后使用$3取第3列与数字10进行比较

输出passwd中各行第1列与第3列的内容:
awk -F: '{print $1,$3}' /etc/passwd
这里print后面跟的变量以逗号(,)分隔,默认输出结果以空格分隔,如果想要以其他方式分隔中间直接插入双引号引入的字符串即可,例如输出结果以->分隔:print $1"->"$3,不分隔:print $1$3

将第一列与第3列使用->连接并将第3列以16进制输出:
awk -F: '{printf "%s->%x\n", $1,$3}' /etc/passwd

条件判断

awk通过条件判断部分的语句来过滤需要对哪些行应用后面紧跟着的命令。由于awk是编程语言,自然会包含很多运行算及逻辑运算符,这里一一列出,常用的其实也就进行比较的关系运行符及逻辑判断运算符,当然awk中也支持if..else..if及for循环。下面列出运行算符:

运算符 描述
= += -= = /= %= ^= *= 赋值
?: C条件表达式
两个竖扛 逻辑或
&& 逻辑与
~ ~! 匹配正则表达式和不匹配正则表达式
< <= > >= != == 关系运算符
空格 连接
+ - 加,减
* / & 乘,除与求余
+ - ! 一元加,减和逻辑非
^ *** 求幂
++ – 增加或减少,作为前缀或后缀
$ 字段引用
in 数组成员

条件判断需要放在执行语句之前
下面举几个例子说明:
打印出第3列小于10的行中的第1列跟第3列,并用tab分隔:
awk -F: '$3<10{print $1"\t"$3}' /etc/passwd

打印出第1列不是root,并且第3列小于10的行中的第1列跟第3列:
awk -F: '$3<10 && $1!="root" {print $1"\t"$3}' /etc/passwd

类似上面的打印,但每行后面紧接着打印该行的第1跟第6列:
awk -F: '$3<10 && $1!="root" {print $1"\t"$3} {print $1,$6}' /etc/passwd

条件判断与内建变量配合使用才能更加灵活

内建变量

变量 描述
$n 当前记录的第n个字段,字段间由FS分隔
$0 完整的输入记录
ARGC 命令行参数的数目
ARGIND 命令行中当前文件的位置(从0开始算)
ARGV 包含命令行参数的数组
CONVFMT 数字转换格式(默认值为%.6g)ENVIRON环境变量关联数组
ERRNO 最后一个系统错误的描述
FIELDWIDTHS 字段宽度列表(用空格键分隔)
FILENAME 当前文件名
FNR 同NR,但相对于当前文件
FS 字段分隔符(默认是任何空格)
IGNORECASE 如果为真,则进行忽略大小写的匹配
NF 当前记录中的字段数
NR 当前记录数
OFMT 数字的输出格式(默认值是%.6g)
OFS 输出字段分隔符(默认值是一个空格)
ORS 输出记录分隔符(默认值是一个换行符)
RLENGTH 由match函数所匹配的字符串的长度
RS 记录分隔符(默认是一个换行符)
RSTART 由match函数所匹配的字符串的第一个位置
SUBSEP 数组下标分隔符(默认值是/034)

其实常用的也就NF、NR、FS、FNR、OFS

这里顺便加两个awk中非常常用的两个关键字BEGIN和END:

  • BEGIN{ 这里面放的是执行前的语句 }
  • END {这里面放的是处理完所有的行后要执行的语句 }

下面举例说明:
输出从第二行开始的第1列及对应的行号,以及每行所具有的总列数:
awk -F: 'NR>2 {print $1 "\tline:" NR"\t"NF}' /etc/passwd
这句等价于:
awk 'BEGIN{FS=":"} NR>2 {print $1 "\tline:" NR"\t"NF}' /etc/passwd
注意那里需要有个BEGIN,如果没有这句而是直接使用FS=”:”,输出结果中会有很多句都不正常,因为相当于在运行awk解析时,还没有设置分隔符,awk会直接采用默认的分隔符,显然会出错,加入BEGIN就是告诉awk这里面的语句需要在任何语句执行前先执行。
但使用-F可以与[]配合来指定多个分隔符,例如一行中即有以空格又有以:分隔,又有以,分隔,则可以使用-F’[ :,]’

输出各行中第3列小于10的行中第1列跟第3列,并将第3列中的数字增加3倍:
k -v times=3 'BEGIN{FS=":"} $3<10 {printf "%-8s %d\n",$1,$3*times}' /etc/passwd
等价于:
k 'BEGIN{FS=":"; times=3} $3<10 {printf "%-8s %d\n",$1,$3*times}' /etc/passwd

输出$1,$2,$3并以->分隔:
awk 'BEGIN{FS=":"} {print $1,$2,$3}' OFS="->" /etc/passwd
注意OFS在’’号的外面

使用正则

awk中通过~表示模式开始,后面跟//包围的正则表达式:
输出第7列中含有usr并且第3列大于10的行的第1,3,7列:
awk -F: '$7 ~/usr/ && $3>10 {print $1,$3,$7}' OFS="\t" /etc/passwd

awk脚本

awk脚本通常由以下3部分构成:

  • BEGIN{ 这里面放的是执行前的语句 }
  • END {这里面放的是处理完所有的行后要执行的语句 }
  • {这里面放的是处理每一行时要执行的语句}

下面这个例子来自于网上:
假如有以下表:

1
2
3
4
5
6
$ cat score.txt
Marry 2143 78 84 77
Jack 2321 66 78 45
Tom 2122 48 77 71
Mike 2537 87 97 95
Bob 2415 40 57 62

统计所有人有总钱数,及平均情况的awk脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/awk -f
#运行前
BEGIN {
math = 0
english = 0
computer = 0

printf "NAME NO. MATH ENGLISH COMPUTER TOTAL\n"
printf "---------------------------------------------\n"
}
#运行中
{
math+=$3
english+=$4
computer+=$5
printf "%-6s %-6s %4d %8d %8d %8d\n", $1, $2, $3,$4,$5, $3+$4+$5
}
#运行后
END {
printf "---------------------------------------------\n"
printf " TOTAL:%10d %8d %8d \n", math, english, computer
printf "AVERAGE:%10.2f %8.2f %8.2f\n", math/NR, english/NR, computer/NR
}

输出结果:

1
2
3
4
5
6
7
8
9
10
NAME    NO.   MATH  ENGLISH  COMPUTER   TOTAL
---------------------------------------------
Marry 2143 78 84 77 239
Jack 2321 66 78 45 189
Tom 2122 48 77 71 196
Mike 2537 87 97 95 279
Bob 2415 40 57 62 159
---------------------------------------------
TOTAL: 319 393 350
AVERAGE: 63.80 78.60 70.00

一个打印九九税法表的例子:

1
seq 9 | sed 'H;g' | awk -v RS='' '{for(i=1;i<=NF;i++)printf("%dx%d=%d%s", i, NR, i*NR, i==NR?"\n":"\t")}'

说实话,假如所处平台上有python,哪怕有C++可能我都会优先选择这些编程语言来处理那些非常复杂的处理,当然对于一些比较简单的情形,这些工具还是非常好用的。
awk的官方手册:http://www.gnu.org/software/gawk/manual/gawk.html

Welcome to my other publishing channels