目录

AWK 编程实例

awk 并非简单的命令,而是一门专为文本处理设计的编程语言,诞生于20世纪70年代的贝尔实验室,名字取自三位创始人Alfred Aho、Peter Weinberger、Brian Kernighan的姓氏首字母。而Alfred Aho也是著名的编译原理(龙书)的作者。

在Linux世界里,文本处理是运维、开发绕不开的日常——从分析日志、提取配置信息到统计数据,都需要高效的工具支撑。而awk 凭借“按字段处理”的核心能力,成为了比grep(单纯匹配)、sed(整行编辑)更灵活的“文本处理瑞士军刀”。作为一款强大的文本分析语言,也是Linux和Unix环境中功能最强大的数据处理工具,没有之一。

编译原理圣书

awk 用法

awk '/pattern/ {action}' 
变量名含义
ARGC 命令行变元个数
ARGV 命令行变元数组
FILENAME 当前输入文件名
FNR 当前文件中的记录号
FS 输入列记录分隔符,默认为一个空格
RS 输入行记录分隔符,默认为\n
NF 当前记录里列个数
NR 目前为止行记录数
OFS 输出列记录分隔符
ORS 输出行记录分隔符

awk 实例

显示文件file中包含101的匹配行

 awk '/101/'               file 
 awk '/101/,/105/'         file
 awk '$1 == 5'             file
 awk '$1 == "CT"'          file #注意必须带双引号
 awk '$1 * $2 >100 '       file 
 awk '$2 >5 && $2<=15'     file

显示文件file的当前记录号、域数和每一行的第一个和最后一个域

 awk '{print NR,NF,$1,$NF,}'    file 
 awk '/101/ {print $1,$2 + 10}' file #显示文件file的匹配行的第一、二个域加10。
 awk '/101/ {print $1$2}'   file
 awk '/101/ {print $1 $2}' file  #显示文件file的匹配行的第一、二个域,但显示时域中间没有分隔符。

通过管道符获得输入,如:显示第4个域满足条件的行

 df | awk '$4>1000000'         

按照新的分隔符“|”进行操作

 awk -F "|" '{print $1}'   file 
 awk 'BEGIN {FS="[: \t|]"} {print $1,$2,$3}' file  #通过设置输入分隔符(FS="[: \t|]")修改输入分隔符。
 Sep="|"
 awk -F $Sep '{print $1}'  file  #按照环境变量Sep的值做为分隔符。   
 awk -F '[ :\t|]' '{print $1}' file  #按照正则表达式的值做为分隔符,这里代表空格、:、TAB、|同时做为分隔符。
 awk -F '[][]'    '{print $1}' file  #按照正则表达式的值做为分隔符,这里代表[、]

通过文件awkfile依次进行控制

 awk -f awkfile  file     
 cat awkfile
/101/{print "\047 Hello! \047"} # 遇到匹配行以后打印 ' Hello! '  \047代表单引号。
{print $1,$2}                   # 因为没有模式控制,打印每一行的前两个域。

显示文件中第一个域匹配101的行

 awk '$1 ~ /101/ {print $1}' file 
 awk '$1 * $2 >100 {print $1}' file 

通过设置输出分隔符(OFS="%")修改输出格式

 awk  'BEGIN { OFS="%"} {print $1,$2}'   file 

表达式1?表达式2:表达式3

 if (表达式1)
     表达式2
 else
     表达式3
 BEGIN 表示在处理任意行之前进行的操作。
 awk 'BEGIN { max=100 ;print "max=" max} {max=($1 >max ?$1:max); print $1,"Now max is "max}' file 

取得文件第一个域的最大值

 awk '{print ($1>4 ? "high "$1: "low "$1)}' file 

匹配行后先将第3个域替换后再显示

 awk '$1 == "Chi" {$3 = "China"; print}' file 
 awk '{$7 %= 3; print $7}'  file  # 将第7域被3除,并将余数赋给第7域再打印。

匹配行后为变量wage赋值并打印

 awk '/tom/ {wage=$2+$3; printf wage}' file 

END表示在所有输入行处理完后再执行

 awk '/tom/ {count++;} 
    END {print "tom was found "count" times"}' file 

gsub函数用于替换,再将结果输出到filename

 awk 'gsub(/\$/,"");gsub(/,/,""); cost+=$4;
       END {print "The total is $" cost>"filename"}' file 
1 2 3 $1,200.00
1 2 3 $2,300.00
1 2 3 $4,000.00

通过if和else if完成条件语句

 awk '{gsub(/\$/,"");gsub(/,/,"");
  if ($4>1000&&$4<2000) c1+=$4;
  else if ($4>2000&&$4<3000) c2+=$4;
  else if ($4>3000&&$4<4000) c3+=$4;
  else c4+=$4; }
  END {printf  "c1=[%d];c2=[%d];c3=[%d];c4=[%d]\n",c1,c2,c3,c4}"' file

通过exit在某条件时退出,但是仍执行END操作

  awk '{gsub(/\$/,"");gsub(/,/,"");
  if ($4>3000&&$4<4000) exit;
  else c4+=$4; }
  END {printf  "c1=[%d];c2=[%d];c3=[%d];c4=[%d]\n",c1,c2,c3,c4}"' file

通过next在某条件时跳过该行,对下一行执行操作

 awk '{gsub(/\$/,"");gsub(/,/,"");
  if ($4>3000) next;
  else c4+=$4; }
  END {printf  "c4=[%d]\n",c4}"' file

把file1、file2、file3的文件内容全部写到fileall中

格式为: 打印文件并前置文件名。
awk '{ print FILENAME,$0 }' file1 file2 file3 > fileall

把合并后的文件重新分拆为3个文件。并与原文件一致

 awk ' $1!=previous { close(previous); previous=$1 }   
  {print substr($0,index($0," ") +1)>$1}' fileall 

通过管道把date的结果送给getline给变量d,然后打印

 awk 'BEGIN {"date"|getline d; print d}'         

通过getline命令交互输入name,并显示出来

 awk 'BEGIN {system("echo \"Input your name:\\c\""); 
        getline d;print "\nYour name is",d,"\b!\n"}'

打印/etc/passwd文件中用户名包含050x_的用户名

 awk 'BEGIN {FS=":"; while(getline< "/etc/passwd" >0) { if($1~"050[0-9]_") print $1}}'

通过while语句实现循环

 awk '{ i=1;while(i<NF) {print NF,$i;i++}}' file 

通过for语句实现循环

 awk '{ for(i=1;i<NF;i++) {print NF,$i}}'   file     

显示一个文件的全路径

 type file|awk -F "/" '
  { for(i=1;i<NF;i++)
  { if(i==NF-1) { printf "%s",$i }
  else { printf "%s/",$i } }}' 

用for和if显示日期

 awk  'BEGIN {
    for(j=1;j<=12;j++)
      { flag=0;
        printf "\n%d月份\n",j;
        for(i=1;i<=31;i++)
          {
           if (j==2&&i>28) flag=1;
           if ((j==4||j==6||j==9||j==11)&&i>30) flag=1;
           if (flag==0) {printf "%02d%02d ",j,i}
          }
      }
}'

用awk统计5秒内的ContextSwitch Interrupt

vmstat -n 1 10|awk '/[0-9*]/ && $11 > 10 {
cs_tal+=$12; int_tal+=$11; num++
} 
END{print "ContextSwitch: ",cs_tal/num, "Interrupt: ", int_tal/num}'
 
 
# ContextSwitch:  15342 / 9 = 1704.67 Interrupt:  2151.33

统计端口总数并排序

#!/bin/sh
sum=0
str=""
for i in $(sed -n -e '/TunnelPorts/s:.*ports="\([^"]*\).*:\1,53,443,80,7193:' \
                  -e 's:,:\n:gp' a | sort -ru -n); do
sum=$((sum+1))
str="$i,$str"
done
str=${str%,}
 
echo "<TunnelPorts count=\"$sum\" ports=\"$str\" />"

利用iptraf统计出来的日志分析

iptraf -i eth1 -t 1 -B -L eth1.log 
#!/bin/sh
DEV=$1
PORT=$2
TIME=$3
 
[ -z $TIME ] && TIME=1
[ -z $DEV ] && echo "$0 ethx port time" && exit 0
 
IP=`ifconfig $DEV|grep "inet addr"|sed -r 's#.*addr:(.*) Bcast.*#\1#g'`
[ ! -z $PORT ] && IP="$IP:$PORT"
 
FILE="/tmp/$DEV.log"
SORT="10"
LIMIT="0"
 
get_bad_ip() {
        awk '/\*\*\*/{next}  {
                if($11~/^'"$NET"'/ && $13~/^'"$IP"'/) f="O_IN";
                if($13!~/^'"$NET"'/) f="OUT";
                if($11!~/^'"$NET"'/) f="IN";
                gsub("(:[^ ]*)?(;)?","");
                t=$6":"f" "$11;
                a[t]+=$8
        }
 
        END {
                for(i in a) print i,a[i]}' $FILE | sort -k3nr | \
                awk '{a[$1]++; if(a[$1]<'"$SORT"' && ($3-'"$LIMIT"')>0 ) \
                b[$1]=b[$1]"\n"a[$1]" "$2" "$3" bytes"
        }
 
        END{
                for(i in b)
                if(i!~/^:/) print i""b[i]"\n"
        }'
}
 
rm -rf /var/lock/iptraf/iptraf*
rm -rf $FILE
iptraf -i $DEV -t $TIME -B -L $FILE && renice -18 `pidof iptraf`
sleep $TIME
kill -9 `pidof iptraf`
get_bad_ip
#!/bin/sh
IP="10.4.1.1"
NET="10.4.1"
FILE="/var/log/iptraf/eth1.log"
SORT="4"
LIMIT="700"
 
awk '/\*/{next}  {
if($11~/^'"$IP"'/ && $13~/^'"$NET"'/) f="O_OUT";
if($11~/^'"$NET"'/ && $13~/^'"$IP"'/) f="O_IN";
if($13!~/^'"$NET"'/) f="OUT";
if($11!~/^'"$NET"'/) f="IN";
gsub("(:[^ ]*)?(;)?","");
t=$6":"f" "$11;
a[t]+=$8
}
 
END {
for(i in a) print i,a[i]}' $FILE | sort -k3nr | \
awk '{a[$1]++; if(a[$1]<'"$SORT"' && ($3-'"$LIMIT"')>0 ) \
b[$1]=b[$1]"\n"a[$1]" "$2" "$3" bytes"
}
 
END{
for(i in b)
if(i!~/^:/) print i""b[i]"\n"
}'

当列相同时合并行

-bash-3.00# cat /tmp/tcp_remote
eth0 21
eth0 80
eth0 22
eth0 21
eth0 22
eth0 53
eth0 7193
eth0 80
eth0 443
eth0 9001
eth0 53
eth0 7193
eth0 80
eth0 443
eth0 21
eth0 9001
cat /tmp/tcp_remote |sort|uniq|awk '{a[$1]=a[$1](a[$1]?",":"@")$2}END{for (j in a) print j a[j]}'

大文件里高效率去重

awk '!seen[$0]++' text > sort.txt

统计当前系统的网络连接状态分类

netstat -antu | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
 
LAST_ACK 14
SYN_RECV 348
ESTABLISHED 70
FIN_WAIT1 229
FIN_WAIT2 30
CLOSING 33
TIME_WAIT 18122
状态描述
CLOSED无连接是活动的或正在进行
LISTEN服务器在等待进入呼叫
SYNRECV|一个连接请求已经到达,等待确认| |SYNSENT应用已经开始,打开一个连接
ESTABLISHED正常数据传输状态
FINWAIT1|应用说它已经完成| |FINWAIT2另一边已同意释放
ITMEDWAIT|等待所有分组死掉| |CLOSING|两边同时尝试关闭| |TIMEWAIT另一边已初始化一个释放
LAST_ACK等待所有分组死掉

awk中调用系统外部命令

#!/bin/sh
DEFINE="ESTABLISH#100 FIN_WAIT#100 TIME_WAIT#100 SYN_RECV#100"
 
for STATE in ESTABLISH TIME_WAIT FIN_WAIT SYN_RECV;do
  NUM=0
  echo ------------------ $STATE --------------------
  netstat -antu|sed 's/::ffff://g'|grep $STATE|awk '{print $5}'|cut -d: -f1| \
  awk '{++S[$1]} END{for(a in S) \
  if( system("grep "a" /etc/whitelist.txt >/dev/null") != 0 && S[a]>'$NUM' ) print a"\t\tconnect num: "S[a]}'
done
 
for i in $DEFINE;do
        STATE=`echo $i|awk -F# '{print $1}'`
        NUM=`echo $i|awk -F# '{print $2}'`
        PORT=`echo $i|awk -F# '{print $3}'`
        if [ -z $PORT ];then
        netstat -antu|sed 's/::ffff://g'|grep $STATE|awk '{print $5}'|cut -d: -f1|awk '{++S[$1]} END{for(a in S) \
        if( system("grep -q "a" /etc/whitelist.txt" ) != 0 && S[a]>'$NUM' ) print a" "S[a]" ""'$STATE'"" "'$NUM'}'| \
        xargs -I[] /usr/sbin/record_ddos.sh [] # 调用另一个程序来分析数据并处理
        else
        netstat -antu|sed 's/::ffff://g'|grep :$PORT|grep $STATE|awk '{print $5}'|cut -d: -f1|awk '{++S[$1]} END{for(a in S) \
        if( system("grep -q "a" /etc/whitelist.txt" ) != 0 && S[a]>'$NUM' ) print a" "S[a]" ""'$STATE'"" "'$NUM'}'| \
        xargs -I[] /usr/sbin/record_ddos.sh []
        fi
done
 
# whitelist.txt是白名单

awk中调用bash中的变量方法

方法一: 双套单引号

TMP=1234
awk '{print "'$TMP'",$0}'  urfile

方法二: 使用-v传递

TMP=1234
awk -v var=$TMP '{print var,$0}' urfile

awk读取前三行和倒数三行

awk -v N=$(wc -l < file.txt) 'NR<=3 || NR>=(N-2)' file.txt

awk做正则匹配字符截取

KEYS=$(sort -k1 /tmp/download.log | awk '/download/{print $1}' | \
       uniq -c|awk '{if($1>3) {st=index($2,"key");   \
       print substr($2,st+4,36)}}')

SED 单行脚本快速的 awk 实现

又拍流量排名统计脚本

awk '{split($7,a,"/");b[a[3]]+=$10} END{for(i in b) print i,b[i]}' /disk/ssd1/logs/b0.upaiyun.com.access.log|sort -k2nr |head -30 \
  | awk '{print $1}'| nslookup| awk '/Non-authoritative answer/{getline;if($1~/upaiyun/) r=$1;else r=$NF; \
  split(r,a,"."); rr=a[1]"."a[2]; print rr}'  | sed -r 's@>@\n@g'|sort -u