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手册解释 | 个人理解 |
---|---|---|
NF | The number of fields in the current input record. | 当前行通过分隔符切割后一共有多少个字段。$NF就代表最后一个字段,相应的$(NF-1)就是倒数第二个字段。 |
NR | The total number of input records seen so far. | 表示当前处理的是第几行。 |
FILENAME | The 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). | 当前处理的文件的文件名。 |
FS | The input field separator, a space by default. | 字段分隔符,默认是空格和制表符。 |
RS | The 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
的行,d
是sed
的一个删除命令操作,!d
就是删除的反向操作,即删除所有不包含server.name
字符串的行。执行sed '/server.name/!d' sed.txt
可以得到server.name=api-service
。
- 第二部分:
s/.*=//
s
在sed
命令中的的含义为替换,后接的/.*=/
是一个正则表达式,匹配的字符串为任何以=
结尾的字符串,显然,这里匹配到的是server.name=
。后面再接一个/
,两个/
之间的字符串为空,即将匹配到的字符串置为空,那么得到的结果就是api-service
。
tr
命令不在该文讨论范畴,可以自行查阅相关资料。这里的tr -d 'r'
意思是删除末尾的换行符。