Linux提供了awk和sed两个强大的命令行文本处理工具,在对大型日志文件分析时特别有用。对文本的处理结合正则表达式可以高效的获取自己想要的日志信息。本文只是对awk、sed和正则表达式在笔者使用过程中遇到的一些比较常用的功能进行记录,更详细的使用方法可以去相应的官方网站查看。

1 awk简单介绍

根据维基百科的介绍

AWK是一种优良的文本处理工具,Linux及Unix环境中现有的功能最强大的数据处理引擎之一。这种编程及数据操作语言的最大功能取决于一个人所拥有的知识。AWK提供了极其强大的功能:可以进行正则表达式的匹配,样式装入、流控制、数学运算符、进程控制语句甚至于内置的变量和函数。

几乎所有的Linux都自带awk命令。它依次处理文件的每一行,并读取里面的每一个字段。对于日志文件那样的每行格式相同的文本文件,awk可能是最方便的工具。

1.1 基本用法

基本语法命令如下:

# 格式
$ awk actions fileName

# 示例
$ awk '{print $0}' test.log

上面的代码中,test.log就是要处理的文本文件。actions即动作,写在一对单引号内,引号内的命令一般需要一对大括号括起来。print是打印命令。

根据以上的基础语法,我们进行一次实践。

 这里顺便学习echo命令如何输出单引号,目前了解到的方式是通过双引号把单引号引起来,然后将单引号两侧的字符串通过单引号引起来,放在一起即可。
$ echo 'I'"'"'m learning awk' | awk '{print $0}'
I'm learning awk

$ echo 'I'"'"'m learning awk' | awk '{print $1}'
I'm

$ echo 'I'"'"'m learning awk' | awk '{print $2}'
learning

以上,我们可以看到,$0代表的是整行,$1默认为第一列,$2$3以此类推。默认的分隔符是空格或制表符,即t的转义字符。

为便于加深理解,我们以/etc/passwd文件(该文件是通过冒号分开的有规律的文件)为例,进行如下操作:

$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin

$ cat /etc/passwd | awk -F':' '{print $1}'
root
bin
daemon

我们通过-F指定行的分隔符,然后输出每行的第一列。

1.2 内置变量

内置变量我们可以通过man awk命令,在结果中搜索Built-in Variables可以查询到所有的内置变量,这里对一些常用的做简单学习。

变量名man手册解释个人理解
NFThe number of fields in the current input record.当前行通过分隔符切割后一共有多少个字段。$NF就代表最后一个字段,相应的$(NF-1)就是倒数第二个字段。
NRThe total number of input records seen so far.表示当前处理的是第几行。
FILENAMEThe name of the current input file. If no files are specified on the command line, the value of FILENAME is “-”. However, FILENAME is undefined inside the BEGIN block (unless set by getline).当前处理的文件的文件名。
FSThe input field separator, a space by default.字段分隔符,默认是空格和制表符。
RSThe input record separator, by default a newline.行的分隔符,默认是换行符。

根据以上的内置变量,对其中的部分进行实践。

 输出第一列和倒数第二列,中间用字符-隔开,双引号中的字符原样输出,如果是逗号,默认为空格
$ cat /etc/passwd | awk -F':' '{print $1"-"$(NF-1)}'
root-/root
bin-/bin
daemon-/sbin

 同上,输出行号
$ cat /etc/passwd | awk -F':' '{print NR ")", $1, $(NF-1)}'
1) root /root
2) bin /bin
3) daemon /sbin

1.3 常用操作

准备文件awk.txt,文件内容如下:

1 12
2 32
2 32
5 12
5 12
6 165
6 165

我们分别对其进行求和和去重操作。

1.3.1 求和

我们对第二列的值进行求和操作:

$ awk 'BEGIN {sum = 0} {sum += $2} END {print sum}' awk.txt 
430

 同样的,可以进行求平均值操作,除以总行数即可
$ awk 'BEGIN {sum = 0} {sum += $2} END {print sum/NR}' awk.txt
61.4286

 格式化输出,同C语言的格式化输出
$ awk 'BEGIN {sum = 0} {sum += $2} END {printf("%.2fn"), sum/NR}' awk.txt
61.43
1.3.2 去重

去重有两种方案可以实现。

# 先使用sort进行排序,然后进行uniq操作
$ sort awk.txt | uniq
1 12
2 32
5 12
6 165

# 使用awk
$ awk '!a[$0]++' awk.txt
1 12
2 32
5 12
6 165

关于!a[$0]++的个人理解:

  • awk数组的下标可以为字符串(经证实,可以。如果不可以,a[$0]也可以理解为下标=hash($0))和数字。
  • 如果action为空,默认action={print $0},上面的去重命令行等价于awk '!a[$0]++ {print $0}' awk.txt
  • ++操作是先使用值,然后自增。

由以上三点,可以得出以下执行逻辑:

读取a[$0]的值,默认为0,进行非操作!a[$0]得到值为1,所以输出$0然后值自增为1,下一次执行的时候,如果a[$0] != 0,进行非操作,得到0,则该行不输出。

1.3.2 条件筛选

如果我们切割出来的列,包含了一些杂质,比如,本来该列均为数字,结果文件内容该位置出现的字母,那么,我们可以进一步进行条件筛选。以如下文本为例:

1 12
2 32
2 32
5 12
5 12
6 165
6 165
h hello

我们的需求是筛选出第二列的所有数字,实现方式如下:

$ awk '{tmp = $2} {if (tmp ~ /[0-9]+/) {print $2}}' awk.txt
# 或者如下
$ awk '/[0-9]+/ {print}' awk.txt 
1 12
2 32
2 32
5 12
5 12
6 165
6 165

~awk中的正则表达式的匹配,相应的还有!~操作符。关于正则表达式,后面会花时间进行初步学习。

2 sed实践

sed不着过多笔墨,这里只对实际场景中用到的进行简单记录。更深层次的学习后续用到的时候在这里继续补充。目前sed在实战中的使用场景是,在shell启动脚本中,读取配置文件的参数,然后通过命令启动应用。有如下配置文件key=value格式,我们需要通过key读取到value。

配置文件sed.txt

server.name=api-service
server.port=8080
server.path=/test

我们需要分别读到服务名称、端口等信息,具体操作如下:

# 读取服务名称
$ sed '/server.name/!d;s/.*=//' sed.txt | tr -d 'r'
api-service

# 读取启动端口
$ sed '/server.port/!d;s/.*=//' sed.txt | tr -d 'r'
8080

# 依次类推

对如上命令的理解和解释,在命令中以分号为界,分为两个部分:

  • 第一部分:/server.name/!d

/server.name/是一个正则表达式,表示包含字符串server.name的行,dsed的一个删除命令操作,!d就是删除的反向操作,即删除所有不包含server.name字符串的行。执行sed '/server.name/!d' sed.txt可以得到server.name=api-service

  • 第二部分:s/.*=//

ssed命令中的的含义为替换,后接的/.*=/是一个正则表达式,匹配的字符串为任何以=结尾的字符串,显然,这里匹配到的是server.name=。后面再接一个/,两个/之间的字符串为空,即将匹配到的字符串置为空,那么得到的结果就是api-service

tr命令不在该文讨论范畴,可以自行查阅相关资料。这里的tr -d 'r'意思是删除末尾的换行符。