Linux作为开源软件的重要代表,文本的处理对其有着非凡的意义,在介绍了VIM、SED后,本文将Linux另一重要的文本处理工具——awk,包括awk命令的使用,以及awk脚本编写,以及常用的awk脚本总结
由于Linux上众多文件以文本形式保存,文本处理几乎是最常见的操作,而其中最知名的工具当属被称为Linux文本处理三剑客的Vim、Sed与AWK了,关于前二者,此前已有介绍:
awk(读音:[ɔːk])得名与其三个作者Alfred Aho,Peter Weinberger和Brian Kernighan姓氏的首字母,属于贝尔实验室的成果,awk工具自有一套语法,遂亦是一门脚本语言,通常我们将其用作数据提取和报告工具
作为编程语言,其支持变量、数组、函数,可进行选择、循环等流程控制
AWK后更名为nawk,而在Linux上使用的为GNU实现的gawk,若非特别说明,以下的awk均指gawk
GAWK(1)中对于该工具的描述为“pattern scanning and processing language”,不同于Sed将文本对每一行进行处理,AWK通过迭代将输入中读取到的每一行的每一个字段分别进行处理,并且将切割的每一段依次以$1
、$2
、$3
、……编号(与Shell编程中的$1
、$2
、$3
、……不同),而$0
则表示所有字段,即当前处理的行
awk的基础使用格式为
awk [OPTIONS] 'PROGRAM' FILE ...
OPTIONS
-F 指定字段分隔符,默认为空白符
-v 用户自定义变量,若需要多个,每一个-v后跟一个自定义变量
PROGRAM awk执行脚本,可指定多个,由;分隔,PROGRAM由两部分组成:
PATTERN{ACTION}
PATTERN 匹配模式,可使用如下方式
/PAT1/,/PAT2/ 地址定界,即从第一次能被PAT1匹配到的行开始,至第一次能被PAT2匹配到的行结束
/PAT/ 能被PAT匹配到的行
!/PAT/ 取反,即不能被PAT匹配到的行
BOOL_EXPRESSION 布尔表达式
BEGIN 即字面值BEGIN,awk脚本运行前执行一次
END 即字面值END,awk脚本运行完成后执行一次
ACTION 执行的操作
print 打印当前行,其后可跟参数,如print $1即打印第一个字段
如,显示UID大于500的用户的用户名
[root@localhost ~]# awk -F: '$3>500{print $1}' /etc/passwd
polkitd
sssd
colord
libstoragemgmt
nfsnobody
chrony
zhangsan
logstash
abc1
abc2
abc3
abc4
abc5
abc6
abc7
abc8
abc9
abc10
tom
gentoo
user2
user3
user1
显示用户shell为bash的用户
[root@localhost ~]# awk -F: 'BEGIN{print "=====Start====="};$7~/bash$/{print $1};END{print "======END======"}' /etc/passwd
=====Start=====
root
zhangsan
logstash
abc1
abc2
abc3
abc4
abc5
abc6
abc7
abc8
abc9
abc10
tom
user2
user3
user1
======END======
其中$7~/bash$/
将/etc/passwd/
文件每行的低7个字段(即用户shell)与模式bash$
匹配,下文将做详细介绍
需要特别指出的是,不同于shell编程,awk中的变量引用不需要加$
FS
: input Field Separator,读取文件本时,所使用字段分隔符,默认为空白字符;
RS
: Record separator,输入时所使用的行分隔符,默认为换行符;
OFS
: Output Filed Separator,输出时使用的分隔符,默认为空白字符;
ORS
:Output Row Separator,输出文本信息所使用的行分隔符,默认为换行符;
如,取出/etc/passwd
第一个字段:awk -v FS=: '{print $1}' /etc/passwd
NR
: theNumber of input Records,awk命令所处理的记录数
NF
:Number of Field,当前记录的field个数;
$
,故若使用$NF
则表示最后一个FieldFNR
:与NR
不同的是,FNR
用于记录正处理的行是当前处理文件的行数
ARGV
: 数组,保存命令行本身这个字符串,如
awk '{print $0}' a.txt b.txt
在这个命令中,ARGV[0]
保存awk,ARGV[1]
保存a.txt;
ARGC
: awk命令的参数的个数;
FILENAME
: awk命令当前正在处理的文件的名称;
ENVIRON
:当前shell环境变量及其值的关联数组;如:
awk 'BEGIN{print ENVIRON["PATH"]}'
gawk允许用户自定义自己的变量以便在程序代码中使用,变量名命名规则与大多数编程语言相同,只能使用字母、数字和下划线,且不能以数字开头。gawk变量名称区分字符大小写
-v
定义awk -v num1=20 -v num2=30 'BEGIN{print num1+num2}'
awk 'BEGIN{num1=20;num2=30;print num1+num2}'
awk '{num1=20;num2=30}{print num1+num2}' /etc/issue
awk 'BEGIN{var="variable testing";print var}'
awk -v var="variable testing" 'BEGIN{print var}'
printf
即Print Formatting,即格式化输出,同print
,其属于PROGRAM中的ACTION,可实现较print
高级的格式输出
其使用格式为:
printf FORMAT, ITEM1, ITEM2, ...
需要说明的是
printf
必须要指定FORMAT
FORMAT
用于指定后面的每个ITEM
的输出格式
printf
语句不会自动打印换行符\n
FORMAT
格式的指示符都以%开头,后跟一个字符;如下:
Format | 描述 |
---|---|
%c |
显示字符的ASCII码 |
%d , %i |
十进制整数 |
%e , %E |
科学计数法显示数值 |
%f |
显示浮点数 |
%g , %G |
以科学计数法的格式或浮点数的格式显示数值 |
%s |
显示字符串 |
%u |
无符号整数 |
%% |
显示%自身 |
另外,在指定格式时,于%
于字符之间可有修饰符,用于限定输出样式,形式如下:
[+|-]M[.N]
+ 显示数值符号
- 左对齐,不指定则为右对齐
M 显示宽度,默认为右对齐
N 小数点后的精度
如:
[root@localhost ~]# awk -F: '{printf "%20s %-s\n",$1,$3}' /etc/passwd
root 0
bin 1
daemon 2
adm 3
lp 4
sync 5
shutdown 6
halt 7
mail 8
operator 11
games 12
ftp 14
nobody 99
avahi-autoipd 170
dbus 81
polkitd 999
abrt 173
sssd 998
colord 997
ntp 38
libstoragemgmt 996
rpc 32
rtkit 172
usbmuxd 113
rpcuser 29
nfsnobody 65534
mysql 27
tss 59
chrony 995
pulse 171
gdm 42
postfix 89
sshd 74
tcpdump 72
zhangsan 1003
apache 48
systemd-network 192
logstash 1007
abc1 1008
abc2 1009
abc3 1010
abc4 1011
abc5 1012
abc6 1013
abc7 1014
abc8 1015
abc9 1016
abc10 1017
named 25
tom 5001
gentoo 5002
user2 5004
user3 5005
user1 5006
[root@localhost ~]# awk -F: '{printf "%-15s %i\n",$1,$3}' /etc/passwd
root 0
bin 1
daemon 2
adm 3
lp 4
sync 5
shutdown 6
halt 7
mail 8
operator 11
games 12
ftp 14
nobody 99
avahi-autoipd 170
dbus 81
polkitd 999
abrt 173
sssd 998
colord 997
ntp 38
libstoragemgmt 996
rpc 32
rtkit 172
usbmuxd 113
rpcuser 29
nfsnobody 65534
mysql 27
tss 59
chrony 995
pulse 171
gdm 42
postfix 89
sshd 74
tcpdump 72
zhangsan 1003
apache 48
systemd-network 192
logstash 1007
abc1 1008
abc2 1009
abc3 1010
abc4 1011
abc5 1012
abc6 1013
abc7 1014
abc8 1015
abc9 1016
abc10 1017
named 25
tom 5001
gentoo 5002
user2 5004
user3 5005
user1 5006
printf
可实现类似shell中的重定向:
print items > output-file
print items >> output-file
print items | command
/dev/stdin
:标准输入
/dev/sdtout
: 标准输出
/dev/stderr
: 错误输出
/dev/fd/N
: 某特定文件描述符,如/dev/stdin
就相当于/dev/fd/0
如
awk -F: '{printf "%-15s %i\n",$1,$3 > "/dev/stderr" }' /etc/passwd
awk
中的操作符与C语言类似,此处仅作总结
-x
: 负值+x
: 转换为数值;x^y
: 乘方x**y
: 乘方x*y
: 乘法x/y
:除法x+y
:加法x-y
:减法x%y
:取模字符串操作符只有一个,而且不用写出来,用于实现字符串连接
=
+=
-=
*=
/=
%=
^=
**=
++
--
注意: 如果某模式为=
号,此时使用/=/
可能会有语法错误,应以/[=]/
替代
~
:是否匹配!~
:是否不匹配比较操作 | 描述 |
---|---|
x < y |
True if xis less than y. |
x <= y |
True if x is less than or equal to y. |
x > y |
True if x is greater than y. |
x >= y |
True if x is greater than or equal to y. |
x == y |
True if x is equal to y. |
x != y |
True if x is not equal to y. |
x ~ y |
True if the string x matches the regexp denoted by y. |
x !~ y |
True if the string x does not match the regexp denoted by y. |
subscript in array |
True if the array array has an element with the subscript subscript. |
&&
:与
||
:或
!
:非
awk
中,任何非0值或非空字符串都为真,反之就为假
selector?if-true-exp:if-false-exp
selector /*条件表达式,若其为真,执行if-true-exp,否则执行if-false-exp
if selector; then
if-true-exp
else
if-false-exp
fi
如,判断系统上用户的UID,若小于1000则显示“Common User”,否则显示“Sysadmin or Sysuser”:
awk -F: '{$3>=1000?usertype="Common User":usertype="Sysadmin or Sysuser";printf "%15s:%-s\n",$1,usertype}' /etc/passwd
awk中的函数调用形式为
function_name(para1,para2,…)
split(string, array [, fieldsep [, seps ] ])
功能:将string
表示的字符串以fieldsep
为分隔符进行分隔,并将分隔后的结果保存至array
为名的数组中;数组下标为从1
开始的序列;如:
string
为root:x:0:0:root:/root:/bin/bash
user[1]=root
user[2]=x
user[7]=/bin/bash
例
netstat -ant | awk '/:80\>/{split($5,clients,":");IP[clients[1]]++}END{for(i in IP){print IP[i],i}}' | sort -rn | head -50
netstat -tan | awk '/:80\>/{split($5,clients,":");ip[clients[4]]++}END{for(a in ip) print ip[a],a}' | sort -rn | head -50
netstat -tan | awk '/^tcp\>/{split($5,ip,":");count[ip[1]]++}END{for(i in count){print i,count[i]}}'
df -lh | awk '!/^File/{split($5,percent,"%");if(percent[1]>=20){print $1}}'
length([string])
string
字符串中字符的个数
substr(string, start [, length])
string
字符串中的子串,从
start
开始,取
length
个
start
从1开始计数
sub(r, s [,t])
r
表示的模式,查找
t
所表示的字符串中,将
第一次匹配到内容替换为
s
所表示的内容
gsub(r, s [,t])
r
表示的模式,查找
t
所表示的字符串中,将所有匹配到内容替换为
s
所表示的内容
system(command)
command
并将结果返回至
awk
命令
rand()
systime()
tolower(s)
s
中的所有字母转为小写
toupper(s)
s
中的所有字母转为大写
自定义函数使用function
关键字,格式如下:
function F_NAME([variable])
{
STATEMENTS
}
此外,函数还可以使用return
语句返回值,格式为return value
awk的使用格式为awk 'program' input-file1 input-file2 ...
其中的program
为:
pattern { action }
pattern { action }
...
此处再次列出常见的模式类型:
1、/Regexp/
: 正则表达式,格式为/regular expression/
,仅处理能被此处的模式匹配到的行,如
awk -F: '$NF~/bash$/{print $1,$NF}' /etc/passwd
2、expresssion
: 表达式,其值非0
或为非空字符时满足条件,如:$1 ~ /foo/ 或 $1 == "Brahming"
,用运算符~
(匹配)和!~
(不匹配),如:
awk -F: '$3>=1000{print $1,$3}' /etc/passwd
awk -F: '$NF~/bash$/{print $1,$NF}' /etc/passwd
3、Line Ranges
: 指定的匹配范围,格式为pat1,pat2
,不支持直接指定行号,可使用:
awk -F: '/^root/,/^user/{print $1}' /etc/passwd
awk -F: '(NR>=2&&NR<=10){print $1}' /etc/passwd
4、BEGIN
,END
:特殊模式,仅在awk命令执行前运行一次或结束前运行一次
BEGIN
:仅在开始处理文件中的文本之前执行一次END
:仅在文本处理完成之后,命令结束之前,执行一次5、Empty
(空模式):匹配任意输入行
if (condition) {then-body}
if (condition) {then-body} else {else-body}
awk '{if ($3==0) {print $1, "Adminitrator";} else { print $1,"Common User"}}' /etc/passwd
awk -F: '{if ($1=="root") print $1, "Admin"; else print $1, "Common User"}' /etc/passwd
awk -F: '{if ($1=="root") printf "%-15s: %s\n", $1,"Admin"; else printf "%-15s: %s\n", $1, "Common User"}' /etc/passwd
awk -F: -v sum=0 '{if ($3>=500) sum++}END{print sum}' /etc/passwd
awk '{if(NF>5) print}' /etc/fstab
awk -F: '{if($NF="/bin/bash") print $1}' /etc/passwd
df -h | awk -F[%] '/^\/dev/{print $1}' | awk '{if($NF>=20) print $1}'
语法:
while (condition){statement1; statment2; ...}
例:
awk -F: '{i=1;while (i<=3) {print $i;i++}}' /etc/passwd
awk -F: '{i=1;while (i<=NF) { if (length($i)>=4) {print $i}; i++ }}' /etc/passwd
awk '{i=1;while (i<=NF) {if ($i>=100) print $i; i++}}' hello.txt
# hello.txt文件的内容为一堆随机数
awk '/^[[:space:]]*linux16/{i=1;while(i<=NF) {if(length($i)>=7) {print $i,length($i)};i++}}' /etc/grub2.cfg
do-while
至少执行一次循环体,不管条件满足与否
语法:
do{statement1, statement2, ...} while (condition)
例:
awk -F: '{i=1;do {print $i;i++}while(i<=3)}' /etc/passwd
awk -F: '{i=4;do {print $i;i--}while(i>4)}' /etc/passwd
语法:
for ( variable assignment; condition; iteration process) { statement1, statement2, ...}
例:
awk -F: '{for(i=1;i<=3;i++) print $i}' /etc/passwd
awk -F: '{for(i=1;i<=NF;i++) { if (length($i)>=4) {print $i}}}' /etc/passwd
awk '/^[[:space:]]*linux16/{for(i=1;i<=NF;i++){print $i,length($i)}}' /etc/grub2.cfg
awk的for
循环还可以用来遍历数组元素:
语法:
for (i in array) {statement1, statement2, ...}
例:统计默认shell
[root@localhost ~]# awk -F: '$NF!~/^$/{BASH[$NF]++}END{for(A in BASH){printf "%15s:%i\n",A,BASH[A]}}' /etc/passwd
/bin/sync:1
/bin/bash:17
/sbin/nologin:32
/sbin/halt:1
/bin/false:1
/bin/csh:1
/sbin/shutdown:1
switch (expression) { case VALUE1 or /REGEXP/: statement1; case VALUE1 or /REGEXP/: statement2; … ; default: statement1, ...}
与其他语言类似,常用于循环或switch
语句中,也可跳出N层嵌套:
break [N]
提前结束对本行文本的处理,并接着处理下一行;例如,下面的命令将显示其ID号为奇数的用户:
awk -F: '{if($3%2==0) next;print $1,$3}' /etc/passwd
退出主输入循环,进入END
,若没有END
或END
中有exit
语句,则退出脚本
数组的概念此处不再赘述,在awk中,数组的引用也与其他语言类似:
array[index-expression]
index-expression
可以使用任意字符串如果某数据组元素事先不存在,那么在引用其时,awk会自动创建此元素并初始化为空串
index in array
的方式要遍历数组中的每一个元素,可使用for
语句,此处再次列出:
for (var in array) { statement1, ... }
其中,var
用于引用数组下标,而不是元素值
array[var]
: 数组中某一元素的值
例:
netstat -ant | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
# 每出现一被/^tcp/模式匹配到的行,数组S[$NF]就加1,NF为当前匹配到的行的最后一个字段,此处用其值做为数组S的元素索引;
awk '{counts[$1]++}; END {for(url in counts) print counts[url], url}' /var/log/httpd/access_log
# 用法与上一个例子相同,用于统计某日志文件中IP地的访问量
awk 'BEGIN{weekdays["mon"]="Monday";weekdays["sun"]="sunday";for(i in weekdays) {print weekdays[i]}}'
# i变量会遍历weekdays的每个索引
awk '{for(i=1;i<=NF;i++){count[$i]++}} END{for(i in count){print i,count[i]}}' /etc/fstab
# 显示文件中每个单词出现的次数
删除数组变量
从关系数组中删除数组索引需要使用delete
命令。使用格式为:
delete array[index]
1.查看TCP连接状态
netstat -nat |awk ‘{print $6}’|sort|uniq -c|sort -rn
netstat -n | awk ‘/^tcp/ {++S[$NF]};END {for(a in S) print a, S[a]}’ 或
netstat -n | awk ‘/^tcp/ {++state[$NF]}; END {for(key in state) print key,"\t",state[key]}’
netstat -n | awk ‘/^tcp/ {++arr[$NF]};END {for(k in arr) print k,"t",arr[k]}’
netstat -n |awk ‘/^tcp/ {print $NF}’|sort|uniq -c|sort -rn
netstat -ant | awk ‘{print $NF}’ | grep -v ‘[a-z]‘ | sort | uniq -c
2.查找请求数请20个IP(常用于查找攻来源):
netstat -anlp|grep 80|grep tcp|awk ‘{print $5}’|awk -F: ‘{print $1}’|sort|uniq -c|sort -nr|head -n20
netstat -ant |awk ‘/:80/{split($5,ip,":");++A[ip[1]]}END{for(i in A) print A[i],i}’ |sort -rn|head -n20
3.用tcpdump嗅探80端口的访问看看谁最高
tcpdump -i eth0 -tnn dst port 80 -c 1000 | awk -F"." ‘{print $1"."$2"."$3"."$4}’ | sort | uniq -c | sort -nr |head -20
4.查找较多time_wait连接
netstat -n|grep TIME_WAIT|awk ‘{print $5}’|sort|uniq -c|sort -rn|head -n20
5.找查较多的SYN连接
netstat -an | grep SYN | awk ‘{print $5}’ | awk -F: ‘{print $1}’ | sort | uniq -c | sort -nr | more
6.根据端口列进程
netstat -ntlp | grep 80 | awk ‘{print $7}’ | cut -d/ -f1
1.获得访问前10位的ip地址
cat access.log|awk ‘{print $1}’|sort|uniq -c|sort -nr|head -10
cat access.log|awk ‘{counts[$(11)]+=1}; END {for(url in counts) print counts[url], url}’
2.访问次数最多的文件或页面,取前20
cat access.log|awk ‘{print $11}’|sort|uniq -c|sort -nr|head -20
3.列出传输最大的几个exe文件(分析下载站的时候常用)
cat access.log |awk ‘($7~/.exe/){print $10 " " $1 " " $4 " " $7}’|sort -nr|head -20
4.列出输出大于200000byte(约200kb)的exe文件以及对应文件发生次数
cat access.log |awk ‘($10 > 200000 && $7~/.exe/){print $7}’|sort -n|uniq -c|sort -nr|head -100
5.如果日志最后一列记录的是页面文件传输时间,则有列出到客户端最耗时的页面
cat access.log |awk ‘($7~/.php/){print $NF " " $1 " " $4 " " $7}’|sort -nr|head -100
6.列出最最耗时的页面(超过60秒的)的以及对应页面发生次数
cat access.log |awk ‘($NF > 60 && $7~/.php/){print $7}’|sort -n|uniq -c|sort -nr|head -100
7.列出传输时间超过 30 秒的文件
cat access.log |awk ‘($NF > 30){print $7}’|sort -n|uniq -c|sort -nr|head -20
8.统计网站流量(G)
cat access.log |awk ‘{sum+=$10} END {print sum/1024/1024/1024}’
9.统计404的连接
awk ‘($9 ~/404/)’ access.log | awk ‘{print $9,$7}’ | sort
cat access.log |awk ‘{counts[$(9)]+=1}; END {for(code in counts) print code, counts[code]}'
cat access.log |awk '{print $9}'|sort|uniq -c|sort -rn
11.蜘蛛分析,查看是哪些蜘蛛在抓取内容
tcpdump -i eth0 -l -s 0 -w - dst port 80 | strings | grep -i user-agent | grep -i -E 'bot|crawler|slurp|spider'