文章源地址
awk命令是Linux系统里非常强大和功能丰富的文本行处理工具命令,使用方法简单,但是功能丰富,处理文本的速度也非常快。awk是Linux及Unix环境中现有的功能最强大的数据处理引擎之一。强大到一个命令工具拥有了自己的语言,Awk的名字来源于它的三个开发者首字母(Alfred Aho, Peter Weinberger, and Brian Kernighan), 这款软件在1977年就有了,一直保留至今足以说明其重要性了。当你遇到文本处理问题一筹莫展时,而你恰恰又看到了awk命令的使用方法介绍,那么你会在这里找到你的解决方法的。
awk语法
awk的基本使用方法如下:
pattern { action }
pattern
就是模式,用来精准的查找到你要处理的数据位置,支持数值判断,字符串比较,正则匹配等。
action
就是要对 pattern
查找到的数据行进行处理的逻辑动作,例如输出找到的行数据、统计行数或者是查找是否有你的名字等等。
awk pattern语法
模式pattern
可以是以下任何任何一种格式:
BEGIN : 内建模式,表示在处理文件之前的模式操作,我们可以在这个模式中执行一些初始化等预先的动作任务。
END : 内建模式,表示在处理文件之后的模式操作,我们可以在这个模式中执行一些展示统计结果等输出的动作任务。
/regular expression/ : 正则表达式,支持大部分正则语法,例如 /^[0-9]/ 匹配首部包含数字的行。
relational expression : 关系表达式,例如 大于,小于,等于的比较 `$1 > 100` 匹配 第一个字段大于100的行。
pattern && pattern : 模式与模式的逻辑与,两模式都真时匹配行。
pattern || pattern : 模式与模式的逻辑或,两模式至少一个为真即可匹配行。
! pattern : 模式的逻辑非, 当模式为假时匹配行, 例如 `! $1 > 10` 匹配的是第一个字段不大于10的行数据。
(pattern) : 括号包含的模式,让模式表达的逻辑关系可以更复杂,例如 `($1~/^ABC/ && $2 > 100) || $1~/^XYZ/` ,匹配 第一列为"ABC"并且第二列大于100的行或者匹配第一列以XYZ开头的行,其实可以更复杂,不过太复杂就不太直观理解,能简单化尽量简单表达。
pattern1, pattern2 : 范围模式匹配,匹配 `pattern1`和`pattern2`之间的行数据。
pattern1 ? pattern2 : pattern3 : 三元运算,这个在Unix系统有可能不支持的,在 `gawk`和`nawk`下通常是支持的, 如果 `pattern1` 为真,则当 `pattern2`为真的行数据匹配成功,如果 `pattern1`为假,则当` pattern3` 为真时匹配行数据, 例如 `$1~/^ABC/ ? $2 > 100 : $1~/^XYZ/` ,预期类似的语法为`($1~/^ABC/ && $2 > 100) || $1~/^XYZ/` ,所以这个是可以通过其他逻辑表达的,很少用到了。
awk 内建变量
NR:已输入记录的条数。
NF:当前记录中域的个数。记录中最后一个域可以以$NF的方式引用。
FILENAME:当前输入文件的文件名。
FS:“域分隔符”,用于将输入记录分割成域。其默认值为“空白字符”,即空格和制表符。FS可以替换为其它字符,从而改变域分隔符。
RS:当前的“记录分隔符”。默认状态下,输入的每行都被作为一个记录,因此默认记录分隔符是换行符。
OFS:“输出域分隔符”,即分隔print命令的参数的符号。其默认值为空格。
ORS:“输出记录分隔符”,即每个print命令之间的符号。其默认值为换行符。
OFMT:“输出数字格式”(Format for numeric output),其默认值为"%.6g"。
awk 内建函数
awk 脚本用法
以一个
hello.awk
脚本文件为例,说明使用awk脚本方法
- 在
awk
脚本第一行添加#!/usr/bin/awk -f
,表示这个脚本需要使用 awk 命令进行解析执行; - 创建脚本后,还需要给脚本文件增加
可执行权限
,命令为chmod +x ./hello.awk
。
增加完可执行权限
后,我们就可以像执行命令一样执行hello.awk
脚本了。
$ chmod +x hello.awk
$ ./hello.awk
Hello, world!
$ cat hello.awk
#!/usr/bin/awk -f
BEGIN { print "Hello, world!" }
如果我们hello.awk
给脚本命名是 cat
的话,这与系统自带的 cat
命令重名了,那么我们怎么知道这个是不是系统命令呢?
$ file ./cat
cat: awk script, ASCII text executable
当然,我们可以直接查看这个cat
文件内容就可以知道它是一个awk
脚本了,然而对于二进制文件
的时候file
命令就非常有帮助啦。
awk 实例
awk 第一个实例
第一个是Hello World!
:
$ echo "Bob"|awk 'BEGIN{ print "Hello "}{ print $0} END{ print "World!"}'
Hello
Bob
World!
-
BEGIN
模式是在处理所有数据之前一定要匹配的模式 -
END
模式是在处理完毕所有数据之后一定要匹配的模式(常常用END进行数据展示输出)。 - 中间的
{print $0}
没有写任何模式,默认会处理所有行数据。
awk 第二个实例:模式的使用
awk '$1~/^[0-9]/ && NF > 1 {
total_result += $1
}
END{
printf("result: %.0f\n", total_result);
}' a.txt
awk 第三个实例:内建变量使用
awk -F'[, ]' 'BEGIN{
## 设置输出域分割符号为冒号:
OFS=":"
}/^[0-9]/ && $1 > 1000{
print FNR,NR,NF,FILENAME
}' b.txt a.txt
3:3:3:b.txt
4:4:2:b.txt
5:5:3:b.txt
6:6:3:b.txt
1:14:10:a.txt
2:15:10:a.txt
3:16:10:a.txt
4:17:10:a.txt
5:18:10:a.txt
- 在
BEGIN
模式中设置了输出域分隔符
变量OFS
为“:”,之后使用print
的默认域分隔符都使用OFS
。 - 处理文件为两个(a.sh 和 a.txt ) , 此时对比
FNR
和NR
的差异了,NF
有时候也会用来格式化过滤一些垃圾数据。
awk 第四个实例: 逻辑判断和循环控制
下面是一个统计英文单词频率的实例,使用两种for
循环语法
$ cat vuln.c
#include
int main(int argc, char *argv[])
{
char buffer[500];
strcpy(buffer, argv[1]);
return 0;
}
$ awk -F"[^a-zA-Z]+" '{
for(i=1; i<=NF; ++i)
{
words[tolower($i)]++
}
}
END {
for(i in words)
{
if( words[i])
{
print i, words[i]
}
}
}' vuln.c
argv 2
char 2
h 1
13
strcpy 1
buffer 2
main 1
argc 1
return 1
stdio 1
include 1
int 2
-
for
循环的语法中,我比较常用的是for( i in words)
这种, 利用哈希(hash
)进行数据存储和查询使用起来非常方便,也经常用到统计分析数据中。
awk 第五个实例: 结合Shell使用
这个grepinawk
脚本执行起来非常类似grep -H
命令:
$ cat grepinawk
pattern=$1
shift
awk '/'$pattern'/ { print FILENAME ":" $0 }' $*
$ ./grepinawk strcpy vuln.c
vuln.c: strcpy(buffer, argv[1]);
$ grep -H strcpy vuln.c
vuln.c: strcpy(buffer, argv[1]);
- 这个
awk
脚本结合了shell
脚本的参数实现了类似grep
命令的功能 -
grepinawk
脚本第一个参数传递给pattern
变量 , 然后通过shift
从参数列表中删去。 -
$*
记录的是去掉了地一个参数的参数列表了,这就达到了grep
命令的简单实现。
awk 第六个实例:awk内建函数的使用
## 统计一下本机都与哪些外部地址有TCP连接方法
netstat -tn | awk '/ESTABLISHED/{
LocalAddress = $4;
ForeignAddress = $5;
## 按照冒号分割 ForeignAddress 字段内容
n = split(ForeignAddress, arr , ":")
if( n == 2)
{
address[ arr[1] ] ++
}
}END{
for( i in address)
{
printf("%-18s %-5.0f\n", i, address[ i ]);
}
}'
## 方法二
netstat -tn | awk '/ESTABLISHED/{
gsub(":", " ");
i=$6;
address[ i ]++
}
END{
for( i in address)
{
printf("%-18s %-5.0f\n", i, address[ i ]);
}
}'
- 这个实例使用了
split
内建函数,可以将数据重新进行拆分成一个数组,然后返回值为数组数量,需要记住,数组下表是从1
开始的,并不是像C语言
从0开始的。 - 方法二通过使用
gsub
函数也实现了同样效果。
awk 第七个实例: 重定向输出和管道的使用
一个awk命令可能有很多输出内容,并且需要保存到不同名称的文件中,这时候就需要使用重定向输出啦:
netstat -tn | awk '/^tcp/{
state = $NF
print $0 > "/tmp/test/"$NF
printf("%-20s %-20s %-s\n", $4,$5,$6) | "sort"
}'
awk 第八个实例: 函数的使用
>>>>>>>>>>>>$ cat b.txt
100 200 300
1000 40302 2130130
12309 10201 10210
1201 102109
12031 102101 2101
10201 1010 111
10 11121
990 1024
4 101
29 4010
11 230
5 102
7 1021 1012 101
>>>>>>>>>>>>$ awk 'function add (n1, n2) {
return n1 + n2
}
$1> 100{
printf("%-8.0f + %-8.0f = %10.0f\n", $1 , $2 , add( $1, $2))
}' b.txt
1000 + 40302 = 41302
12309 + 10201 = 22510
1201 + 102109 = 103310
12031 + 102101 = 114132
10201 + 1010 = 11211
990 + 1024 = 2014
>>>>>>>>>>>>$