awk 是一种编程语言,用于在linux/unix下对文本和数据进行处理,它能提供一个类编程环境来修改和组织文件中的数据。它比sed功能更为强大,可以看看sed的使用:
Linux sed命令(一)基础
Linux sed命令(二)进阶
awk分别代表其作者姓氏的第一个字母,它的作者是三个人,分别是Alfred Aho、Brian Kernighan、Peter Weinberger。gawk是awk的GNU版本,在所有的发行版中默认没有安装gawk,我们可以手动安装。
使用awk,你可以做以下事情:
- 定义变量来保存数据
- 使用算数和字符串操作符来处理数据
- 使用结构化编程的概念的逻辑来处理数据
- 通过提取数据文件中的数据元素,将其重新排列或格式化,生成格式化报告
一、awk命令格式
awk命令格式如下:
awk [options] program_script file
option指定命令行选项,如:
-F fs 指定行中划分数据字段的字段分隔符
-f file 从指定的文件中读取awk程序
-v var=value 指定awk程序中的一个变量及其默认值
...
我们会在探索awk时逐渐的使用这些命令行选项。
awk强大之处在于program程序脚本,可以用脚本来读取文本行的数据,然后处理并显示数据,创建任何类型的报告。awk程序中脚本使用一对大括号{}来定义,并且由于awk命令行假定脚本是单个文本字符串,你还必须将脚本放在单引号中,即 '{ program_script}'
。
数据字段变量:
awk会将如下变量给配给它在文本行中发现的数据字段:
$0 代表整个文本行
$1 代表文本行中的第1个字段
$2 代表文本行中的第2个字段
...
$n 代表文本行中的第n个字段
在文本行中,每个字段都是通过字段分隔符划分的,默认的字段分隔符是空白字符(如空格、制表符)。当然,我们可以通过-F选项修改字段分隔符。请看示例,我们使用这一行命令awk -F: -f script1.awk /etc/passwd
来输出用户名及其目录,注意首先用-F改变了字段分隔符,然后又用-f指定了脚本,脚本中只有一行代码 {print $1 "'s home is " $6}
,将脚本命名为.awk只是为了便于我们区分。运行它,可以得到类似下面的输出:
root's home is /root
daemon's home is /usr/sbin
bin's home is /bin
sys's home is /dev
...
BEGIN和END使用:
默认情况下,awk会从输入中读取一行文本然后针对这一行的数据执行程序脚本。有时可能需要在处理数据前或后运行脚本,比如创建标题和添加页脚说明等。这时我们就要使用BEGIN和END关键字。
# script1.awk
BEGIN {
print "This is the title line"
print "Change the FS"
FS=":"
}
{print $1 "'s home is " $6}
END{
print "This is the end line."
}
# 执行命令
awk -f script1.awk /etc/passwd
如上代码所示,我们在脚本里使用了BEGIN和END,并且在BEGIN中指定了分隔符FS。在终端运行命令后,会发现输出的首末行会有我们在BEGIN和END中写的提示语。
二、awk中的变量
awk中使用支持两种不同的类型变量:内建变量和自定义变量。
2.1、內建变量
使用內建变量来引用程序数据里的一些特殊功能。
1、字段和记录分隔符变量
数据字段(field)是由字段分隔符(field separator)来划定的,前面我们提到过。gawk的输入可以从标准输入或指定的档案里读取,输入的读取单位被称为”记录”(records),gawk 在做处理时,是一个记录一个记录地处理。每个记录的默认值是一行(line),一个记录又被分为多个字段(fields)。
我们既然可以修改字段分隔符(field separator)来修改字段的读取方式,那么也可以修改记录分隔符(record separator)来分隔记录。
变量 | 描述 |
---|---|
FIELDWIDTHS | 由空格分隔的一列数字,定义每个数据字段的宽度 |
FS | 输入字段分隔符(默认是空格和tab) |
RS | 输入记录分隔符(默认是换行) |
OFS | 输出字段分隔符 |
ORS | 输出记录分隔符 |
~
FIELDWIDTHS允许你不依靠字段分隔符来读取记录,一旦设置了FIELDWIDTHS,就会忽略FS变量,并且不能再次改变,不适合用于变长的字段。
#data1
AAAAAHjgisg jkgkj
232 jlj
hoihgl
5555-2424
BBBBLhojlgs jk
435 jk
onnaga
5252-2525
CCCCQjlsjt jlkg
435 jlkjl
opjgsn
4521-5929
如上所示,我们如果要把每四行当做一个记录读取呢?很显然FS应该设置为换行符\n,那么RS应该怎么设置呢?我们可以用空白行来当做记录分隔符。可以写出以下awk代码:
BEGIN {
FS="\n";RS="";OFS="-"
}
{print $1,$4}
END{
print "This is the end line."
}
运行之后得到输出,我们使用了输出字段分隔符-来分隔输出,如下图:
2、数据变量
除了FS和RS等一系列变量,还有一些內建变量来帮助你了解数据发上了什么变化。主要的变量如下表:
变量 | 描述 |
---|---|
ARGC | 当前命令行参数个数,ARGument COUNT |
ARGV | 包含命令行参数的数组 |
ARGIND | 当前文件在ARGV中的位置 |
ENVIRON | 当前shell环境变量及其值组成的关联数组 |
FILENAME | 用作awk输入数据的数据文件的文件名 |
FNR | 当前数据文件中的数据行数 |
NF | 数据文件中字段总数 |
NR | 已处理的输入记录数 |
… | … |
~
见如下使用NF和NR的示例,当我们不知道一条记录里面的字段时,可以用$NF来引用最后一个字段;没处理一条记录时NR的值就会加1,注意在脚本中引用awk变量时,变量名前不用加美元符号,这和shell中引用变量不同,如下面的NR。
#script1.awk
BEGIN {
FS=":";OFS="-"
}
{print $1,$NF,NR}
END{
}
#awk脚本
awk -f script1.awk /etc/passwd
执行后,可得到以下期望的输出:
2.2、自定义变量:
1、在脚本中给变量赋值
和shell脚本中一致,有赋值语句即可。并且还支持数值运算,如下:
2、在命令行中给变量赋值
如下所示,我们在BEGIN和命令当中用到了变量n,但是还没有定义,可以在命令行中用n=
的方式来为n指定数值。
BEGIN {
print "Print the ",n," field."
}
{print $n}
END{
}
需要注意的是在命令行中设置了变量值以后,BEGIN中并不可以直接用!这时我们要在变量定义的前面加上-v 来在BEGIN代码之前设定变量。
三、awk使用数组
awk中使用的是关联数组,它和数字数组不同的是索引可以是任意文本字符串。其实,关联数组和字典、散列表这些东西是一个概念的。
1、定义数组变量
var[key] = value
2、遍历数组变量
和多数语言一样,遍历数组采取for循换即可:
for(var in array)
{
statements
}
输出如下所示,可以看出对数组的支持还是不错的,足够我们使用。
lupu@ubuntu:~$ awk -f script1.awk
key: Onlsgn value: Lkongn
key: Ajgsl value: Ihono
key: Qlgns value: Pnwl
key: 1 value: 1
key: 2 value: 2
key: 3 value: 3
4
1
3、删除数组变量
删除数组索引就可以了:delete array[key]
四、使用模式匹配
awk中同样支持使用模式匹配来过滤数据记录,这和sed中的很相似。
1、正则表达式(re)
使用re时,必须使它出现在它要控制的程序脚本的左花括号前。使用起来很简单,如下图所示:
2、匹配操作符
使用匹配操作符来更精确的控制将要匹配的字段,$n ~ /expression/
,即这里匹配了满足第n个字段是expression的情况。请看以下简单示例:
#匹配/etc/passwd文件中第一个字段一sys开头的记录,并输出第一个字段
lupu@ubuntu:~$ awk -F: '$1 ~ /^sys/{print $1}' /etc/passwd
sys
systemd-timesync
systemd-network
systemd-resolve
systemd-bus-proxy
syslog
我们也可以用!来排除正则表达式的匹配,$n ! ~ /expression/
。
3、数学表达式
当匹配数据字段中有数值时,用数学表达式来匹配非常方便。比如我们用命令ps -ef > ps.log
将当前进程信息写入文件中,如果我们需要找出UID大于9000的进程那么可以这么来写命令:awk '$2 > 9000{print $0}' ps.log
。
可以使用任何比较常见的表达式。
- x == y
- x <= y
- x >= y
- x < y
- x > y
也可以对文本数据使用表达式,但是必须注意的是,只有表达式完全一样才可以匹配。不能使用正则表达式。