awk 是一个文本处理的利器,功能十分强大,awk的脚本可以使用类似c语言的语法编程。内置很多丰富的函数,处理文本十分方便。
下面是我使用linux脚本配合awk脚本写的分析日志的示例,日后可以作为参考。
log_analysis.sh日志分析脚本:
#!/bin/bash
# 日志分析工具
# 使用方法:只需要把日志和本脚本放在同一个目录,然后执行:./log_analysis.sh
# 本脚本需要配合cal.awk文件使用,cal.awk为awk命令的脚本文件
#先查找离线信息
OFFLINE_LIST=`grep -i "offline,hasConneId" *.log | awk -v f1='[' -v f2='Dcm(' '{ time=substr($0,index($0,f1)+1,19); v=substr($0,index($0,f2)+length(f2)); dcmNo=substr(v,1,index(v,")")-1); printf("%s %s\\n",time,dcmNo);}'`
# 也可以用sed命令来实现: grep "offline,hasConneId" *.log | sed -e 's/^.*:\[//' | sed -e 's/\]\[.*Dcm(/ /' | sed -e 's/) (.*$//'
#使用时用(双引号一定要加,否则会打印成一行): echo -e "$OFFLINE_LIST"
#统计离线次数,并按离线次数从大到小排序
OFFLINE_STA=`echo -e "$OFFLINE_LIST" | awk '{a[$2]++} END{for(key in a){print "dcmNo: "key, "离线次数: "a[key] | "sort -rn -k 4"}}'`
# 输出
echo "==========离线次数统计=========="
echo -e "$OFFLINE_STA"
# 查找登录记录,并按时间排序
LOGIN_LIST=`grep -i "Login check" *.log | awk -v f1='[' -v f2='dcmNo:' '{ time=substr($0,index($0,f1)+1,19); v=substr($0,index($0,f2)+length(f2)); dcmNo=substr(v,1,index(v,f1)-1); printf("%s %s\n",time,dcmNo);}' | sort -k 1`
# 依次查找掉线记录及掉线持续时间
echo -e "$OFFLINE_LIST" | awk -v loginList="$LOGIN_LIST" -f cal.awk
#测试cal.awk脚本
#echo -e "20220320_042811_327 869516056100980" | awk -v loginList="20220320_042810_327 869516056100980\n20220320_042813_327 869516056100980" -f cal.awk
cal.awk脚本:
#!/bin/awk -f
BEGIN{
print "==========离线时间统计========";
LINE_NUM=1;
}
{
cmd=sprintf("echo -e \"%s\"|grep %s",loginList,$2);
n=1;
#将命令的输出逐行写入到arr数组
while(cmd|getline tmpStr)
{
arr[n++]=tmpStr;
};
close(cmd);
#将字符串的时间转换为时间戳
timeStr=$1;
year=substr(timeStr,1,4);
month=substr(timeStr,5,2);
day=substr(timeStr,7,2);
hour=substr(timeStr,10,2);
minute=substr(timeStr,12,2);
sec=substr(timeStr,14,2);
tsOffline=mktime(sprintf("%s %s %s %s %s %s",year,month,day,hour,minute,sec));
found=0;
strOfflineTime=strftime("%Y-%m-%d %H:%M:%S",tsOffline);
for(k in arr)
{
split(arr[k],arrItem," ");
timeStr=arrItem[1];
year=substr(timeStr,1,4);
month=substr(timeStr,5,2);
day=substr(timeStr,7,2);
hour=substr(timeStr,10,2);
minute=substr(timeStr,12,2);
sec=substr(timeStr,14,2);
tsLogin=mktime(sprintf("%s %s %s %s %s %s",year,month,day,hour,minute,sec));
if(tsLogin >= tsOffline)
{
found=1;
strLoginTime=strftime("%Y-%m-%d %H:%M:%S",tsLogin);
printf("%d dcmNo: %s 离线时间: %s 最近登录时间: %s 离线持续时间: %d 秒\n", LINE_NUM++,$2,strOfflineTime,strLoginTime,tsLogin-tsOffline);
break;
}
}
if(0 == found)
{
printf("%d dcmNo: %s 离线时间: %s 最近登录时间: not_found 离线持续时间: - 秒\n", LINE_NUM++,$2,strOfflineTime);
}
}
其中用到awk比较关键的点:
1、 如何在awk脚本内部调用shell命令
在awk脚本内存调用shell命令,有几种做法
(1)第一种方法:使用system脚本函数
ps -ef | grep redis | awk '{ cmd="kill -9 "$2;system(cmd) }'
在awk执行体的内部,引用变量,不需要加$符号
如果执行完命令后,需要获取命令的返回值,可以在system()命令前用 var=system();获取,例如:
ps -ef | grep redis | awk '{ cmd="kill -9 "$2; cmd_return_value=system(cmd) }'
system函数的返回值只是表示命令成功和失败,通常是一个int类型的值
(2)第二种方法:拼接命令然后直接执行
除了可以使用system函数执行shell命令之外,还可以直接拼接命令存放到一个变量中,然后执行,如:
ps -ef | grep IOV-NAddSMS | grep -v grep | awk '{ cmd="basename "$8; cmd|getline exename;}'
(3)执行完命令后捕获输出
【1】执行命令后捕获输出使用awk 的getline命令
在awk脚本内部执行shell命令后,捕获输出使用管道和getline命令,如:
ps -ef | grep IOV-NAddSMS | grep -v grep | awk '{ cmd="basename "$8; cmd|getline exename;}'
这样命令输出就会存放到exename这个变量中,而不是输出到屏幕中
注意:getline只会获取到一行的输出,如果获取所有的输出需要使用循环
【2】使用getline捕获多行输出
例子如下:
[root@localhost log]# echo "" | awk '{cmd="ls -l"; while(cmd|getline lineData){printf("line:%s\n",lineData);};close(cmd);}'
line:总用量 27976
line:-rw-r--r--. 1 root root 1737084 4月 6 19:20 20220320.tar.gz
line:-rw-r--r--. 1 root root 1392 4月 7 10:24 cal.awk
line:-rwxr-xr-x. 1 root root 0 3月 20 00:00 ErrorLog.log
line:-rwxr-xr-x. 1 root root 16791268 3月 20 13:03 261.log
line:-rwxr-xr-x. 1 root root 10100762 3月 21 00:09 366.log
line:-rwxr-xr-x. 1 root root 1453 4月 7 10:30 log_analysis.sh
2、awk 脚本内部如何获取外部变量
awk获取外部变量的方法,其中一种方法可以使用awk自带的-v参数,将外部变量转换成内部变量,相当于传递变量值
例如:
[root@KF-CFT-mongdb3 Script]# echo $myvar
IOV-NAddSMS
[root@KF-CFT-mongdb3 Script]# date | awk -v var=$myvar '{ print var }'
IOV-NAddSMS
[root@KF-CFT-mongdb3 Script]#
3、awk 数组
【1】awk数组支持任意类型作为下标,例如下面的数组就是合法的:arr[“name”]=”jkm”
【2】数组遍历
awk的数组都是类似于map,key<->value的形式,如果是普通的数组,key(即下标)为1~n的数字,key也可以是字符串或者其他类型。
遍历数组的格式如下:
[root@localhost log]# echo " " | awk '{arrStr="hello\nworld";split(arrStr,arr,"\n"); for(k in arr){print k,arr[k]}}'
1 hello
2 world
4、使用awk 去除重复行
比较简洁的写法是下面这样:
echo -e "aaa\nbbb\naaa" | awk '!a[$0]++'
但是上面的写法比较难理解,比较易懂的写法如下:
echo -e "aaa\nbbb\naaa" | awk '{ if (!a[$0]++) { print $0 } }'
5、awk 脚本内部给外部变量赋值
awk可以给外部变量赋值,使用awk的内置命令printf命令合成赋值语句,然后使用eval命令将文本的赋值语句执行,成为shell的变量,例如:
[root@localhost ~]# eval `echo "hello world" | awk '{ printf("VAR1=%s\nVAR2=%s",$1,$2); }'`
[root@localhost ~]# echo $VAR1
hello
[root@localhost ~]# echo $VAR2
world
其中printf函数中的\n换行符是必须的,因为必须要每个变量占用一行
其中,eval可以这么用
[root@localhost ~]# eval VAR1=45
[root@localhost ~]# echo $VAR1
即可以把文本的赋值语句转成实际的变量
参考文档: