【Shell脚本编程(六)】文本处理三剑客之awk

1. awk工作模式介绍

1.1. 简介

  • awk是一个文本处理工具,通常用于处理数据并生成结果报告;
  • awk的命名是它的创始人 Alfred Aho、Peter Weinberger和Brian Kernighan 姓氏的首个字母组成的;

1.2. 语法格式

  • 第一种形式:awk ‘BEGIN{}pattern{commands}END{}’ file_name
  • 第二种形式:standard output | awk ‘BEGIN{}pattern{commands}END{}’

语法格式说明

语法格式解释
BEGIN{}正式处理数据之前执行
pattern匹配模式
{commands}处理命令,可能多行
END{}处理完所有匹配数据后执行

2. awk的内置变量

2.1. 内置变量对照表

内置变量含义
$0整行内容
1 − 1- 1n当前行的第1-n个字段
NF(Number Field)当前行的字段个数,也就是有多少列
NR(Number Row)当前行的行号,从1开始计数
FNR(File Number Row)多文件处理时,每个文件行号单独计数,都是从0开始
FS(Field Separator)输入字段分隔符。不指定则默认以空格或tab分割
RS(Row Separator)输入行分隔符。默认回车换行\n
OFS(Output Filed Separator)输出字段分隔符。默认为空格
ORS(Output Row Separator)输出行分隔符,默认为回车换行
FILENAME当前输入的文件名字
ARGC命令行参数个数
ARGV命令行参数数组

3. awk格式化输出之printf

3.1. printf的格式说明符

格式符含义
%s打印字符串
%d打印十进制
%f打印一个浮点数
%x打印十六进制
%o打印八进制
%e打印数字的科学计数法形式
%c打印单个字符的ASCII码

格式符示例:

  1. 以字符串格式打印/etc/passwd中的第7个字段,以“:”作为分隔符;
awk 'BEGIN{FS=":"} {printf "%s",$7}' /etc/passwd
  1. 以10进制格式打印/etc/passwd中的第3个字段,以“:”作为分隔符;
awk 'BEGIN{FS=":"} {printf "%d\n",$3}' /etc/passwd
  1. 以浮点数格式打印/etc/passwd中的第3个字段,以“:”作为分隔符;
awk 'BEGIN{FS=":"} {printf "%f\n,$3"}' /etc/passwd
  1. 以16进制数格式打印/etc/passwd中的第3个字段,以“:”作为分隔符;
awk 'BEGIN{FS=":"} {printf "%#x\n,$3"}' /etc/passwd
  1. 以8进制数格式打印/etc/passwd中的第3个字段,以“:”作为分隔符;
awk 'BEGIN{FS=":"} {printf "%#o\n,$3"}' /etc/passwd
  1. 以科学计数法格式打印/etc/passwd中的第3个字段,以“:”作为分隔符;
awk 'BEGIN{FS=":"} {printf "%e\n,$3"}' /etc/passwd

3.2. printf的修饰符

修饰符含义
-左对齐
+右对齐
#显示8进制在前面加0,显示16进制在前面加0x

修饰符示例:

  1. 左对齐格式
  2. 右对齐格式
  3. 打印8进制或16进制数字是在前面加#

4. awk模式匹配的两种用法

4.1. 模式匹配的两种用法

  • 第一种模式匹配:RegExp
  • 第二种模式匹配:关系运算匹配

4.2. 用法格式对照表

语法格式含义
RegExp按正则表达式匹配
关系运算按关系运算匹配

1. RegExp

  • 匹配/etc/passwd文件行中含有root字符串的所有行;
awk 'BEGIN{FS=":"}/root/{print $0}' /etc/passwd
  • 匹配/etc/passwd文件行中以yarn开头的所有行;
awk 'BEGIN{FS=":"}/^yarn/{print $0}' /etc/passwd

2. 运算符匹配

关系运算符匹配:

<       小于
>       大于
<=      小于等于
>=      大于等于
==      等于
!=      不等于
~       匹配正则表达式
!~      不匹配正则表达式
    1. 以:为分隔符,匹配/etc/passwd文件中的第3个字段小于50的所有行信息;
    awk 'BEGIN{FS=":"}$3<50{print $0}' /etc/passwd
    
    1. 以:为分隔符,匹配/etc/passwd文件中的第3个字段大于50的所有行信息;
    awk 'BEGIN{FS=":"}$3>50{print $0}' /etc/passwd
    
    1. 以:为分隔符,匹配/etc/passwd文件中的第7个字段为/bin/bash的所有行信息;
    awk 'BEGIN{FS=":"}$7=="/bin/bash"{print $0}' /etc/passwd
    
    1. 以:为分隔符,匹配/etc/passwd文件中第7个字段不为/bin/bash的所有行信息;
    awk 'BEGIN{FS=":"}$7!="/bin/bash"{print $0}' /etc/passwd
    
    1. 以:为分隔符,匹配/etc/passwd中第3个字段包含3个以上数字的所有行信息;
    awk 'BEGIN{FS=":"}$3~/[0-9]{3,}/{print $0}' /etc/passwd
    

布尔运算符匹配:

||&&!
    1. 以:为分隔符,匹配/etc/passwd文件中包含hdfs或yarn的所有行信息;
    awk 'BEGIN{FS=":"}$1=="hdfs" || $1=="yarn" {print $0}' /etc/passwd
    
    1. 以:为分隔符,匹配/etc/passwd文件中第3个字段小于50并且第4个字段大于50的所有行信息;
    awk 'BEGIN{FS=":"}$3<50 && $4>50 {print $0}' /etc/passwd
    

5. awk动作中的表达式用法

5.1. awk动作表达式中的算术运算符

运算符含义
+
-
*
/
%
^或**乘方
++x在返回x变量之前,x变量加1
x++在返回x变量之后,x变量加1
–x在返回x变量之前,x变量减1
x–在返回x变量之后,x变量减1
  1. 使用awk计算/etc/services中的空白行数量;
awk '/^$/{sum++}END{print sum}' /etc/services
  1. 计算学生课程分数平均值,学生课程文件内容如下:
Allen   80  90  96  98
Mike    93  98  92  91
Zhang   78  76  87  92
Jerry   86  89  68  92
Han     85  95  75  90
Li      78  88  98  100

#输出格式
Allen   80  90  96  98  计算后的平均值
awk 'BEGIN{printf "%-8s%-8s%-8s%-8s%-8s%s\n","Name","Chinese","Math","English","Pysical","Average"}{total=$2+$3+$4+$5;AVG=total/4;printf "%-8s%-8d%-8d%-8d%-8d%0.2f\n",$1,$2,$3,$4,$5,AVG}' student.txt

6. awk动作中的条件及循环语句

6.1. 条件语句

if(条件表达式)
    动作1
else if(条件表达式)
    动作2
else 
    动作3

6.2. 循环语句-while

while

while(条件表达式)
    动作

6.3. 循环语句-do while

do while

do
    动作
while(条件表达式)

6.4. 循环语句-for

for

for(初始化计数器;测试计数器;计数器变更)
    动作
  1. 以:为分隔符,只打印/etc/passwd中第3个字段的数值在50-100范围内的行信息;
vim scrips.awk

BEGIN{
    FS=":"
}

{
    if($3<50)
    {
        printf "%-20s%-25s%-5d\n","UID<50",$1,$3
    }
    else if($3>50 && $3<100)
    {
        printf "%-20s%-25s%-5d\n","50<UID<100",$1,$3
    }
    else
    {
        printf "%-20s%-25s%-5d\n","UID>100",$1,$3
    }
}

#运行scrips.awk脚本文件
awk -f scrips.awk /etc/passwd
  1. 计算下列同学的平均分数,并且只打印平均分数大于90的同学姓名和分数信息(student.txt);
Name    Chinese English Math    Physical    Average
Allen   80      90      96      98
Mike    93      98      92      91
Zhang   78      76      87      92
Jerry   86      89      68      92
Han     85      95      75      90
Li      78      88      98      100
vim student.awk

BEGIN{
    printf "%-10s%-10s%-10s%-10s%-10s%-10s\n","Name","Chinese","English","Math","Physical","Average"
}

{
    total=$2+$3+$4+$5
    avg=total/4
    if(avg>90)
    {
        printf "%-10s%-10d%-10d%-10d%-10d%-0.2f\n",$1,$2,$3,$4,$5,avg
    }
}

#脚本文件执行命令
awk -f student.awk student.txt


#在以上需求上并且求各科成绩的和
vim student.awk

BEGIN{
    printf "%-10s%-10s%-10s%-10s%-10s%-10s\n","Name","Chinese","English","Math","Physical","Average"
}

{
    total=$2+$3+$4+$5
    avg=total/4
    if(avg>90)
    {
        printf "%-10s%-10d%-10d%-10d%-10d%-0.2f\n",$1,$2,$3,$4,$5,avg
        score_chinese+=$2
        score_english+=$3
        score_math+=$4
        score_physical+=$5
    }
    
}

END{
    printf "%-10s%-10d%-10d%-10d%-10d\n","",score_chinese,score_english,score_math,score_physical
}

#执行脚本文件命令
awk -f student.awk student.txt
  1. 计算1+2+3+4+…+100的和,请使用while、do while、for三种弄循环方式实现;
#while循环方式实现
vim while.awk

BEGIN{
    while(i<=100){
        sum+=i
        i++
    }
    print sum
}

#脚本文件执行命令
awk -f while.awk


#for循环方式实现
vim for.awk

BEGIN{
    for (i=0;i<=100;i++)
    {
        sum+=i
    }
    print sum
}

#脚本文件执行命令
awk -f for.awk


#do-while循环方式实现
vim do-while.awk

BEGIN{
    do
    {
        sum+=i
        i++
    }while(i<=100)
    print sum
}

#脚本文件执行命令
awk -f do-while.awk

7. awk中的字符串函数

7.1. 字符串函数对照表

函数名解释函数返回值
length(str)计算字符串长度整数长度值
index(str1,str2)在str1中查找str2的位置返回值为位置索引,从1计数
tolower(str)转换为小写转换后的小写字符串
toupper(str)转换为大写转换后的大写字符串
substr(str,m,n)从str的m个字符开始,截取n位截取后的子串
split(str,arr,fs)按fs切割字符串,结果保存arr切割后的子串的个数
match(str,RE)在str中按照RE查找,返回位置返回索引位置
sub(RE,RepStr,str)在str中搜索符合RE的子串,将其替换为RepStr;只替换第一个替换的个数
gsub(RE,RepStr,str)在str中搜索符合RE的子串,将其替换为RepStr;替换所有替换的个数
  1. 以:为分隔符,返回/etc/passwd中每行中每个字段的长度;
#需处理的文件内容,例如
root:x:0:0:root:/root:/bin/bash
4:1:1:1:4:5:9

vim example_1.awk

BEGIN{
    FS=":"
}
{
    i=1
    while(i<=NF)
    {
        if(i==NF)
            printf "%d",length($i)
        else
            printf "%d:",length($i)
        i++
    }
    print ""
}

#脚本文件执行命令
awk -f example_1.awk /etc/passwd
  1. 搜索字符串"I have a dream"中出现"ea"子串的位置;
(1)、awk 'BEGIN{str="I have a dream";location=index(str,"ea");print location}'

(2)、awk 'BEGIN{str="I have a dream";location=match(str,"ea");print location}'
  1. 将字符串"Hadoop is a bigdata Framework"全部转换为小写;
awk 'BEGIN{str="Hadoop is a bigdata Framework";print tolower(str)}'
  1. 将字符串"Hadoop is a Bigdata Framework"全部转换为大写;
awk 'BEGIN{str="Hadoop is a bigdata Framework";print toupper(str)}'
  1. 将字符串"Hadoop kafka Spark Storm HDFS YARN Zookeeper",按空格为分隔符,分隔每部分保存到数组array中;
awk 'BEGIN{str="Hadoop kafka Spark Storm HDFS YARN Zookeeper";split(str,arr);for(a in arr) print arr[a]}'
  1. 搜索字符串"Tranction 2345 Start:Select * from master"第一个数字出现的位置;
awk 'BEGIN{str="Tranction 2345 Start:Select * from master";location=match(str,/[0-9]/);print location}'
  1. 截取字符串"transaction start"的子串,截取条件从第4个字符开始,截取5位;
awk 'BEGIN{str="transaction start";print substr(str,4,5)}'

#从第4个字符开始,截取剩下所有的字符
awk 'BEGIN{str="transaction start";print substr(str,4)}'
  1. 替换字符串"Tranction 243 Start,Event ID:9002"中第一个匹配到的数字串为$符号;
awk 'BEGIN{str="Tranction 243 Start,Event ID:9002";count=sub(/[0-9]+/,"$",str);print count,str}'

#替换所有的
awk 'BEGIN{str="Tranction 243 Start,Event ID:9002";count=gsub(/[0-9]+/,"$",str);print count,str}'

8. awk中的常用选项

8.1. awk选项总结

选项解释
-v参数传递
-f指定脚本文件
-F指定分隔符
-V查看awk的版本号
num1=20
var="hello world"

awk -v num2=$num1 -v var1="$var" 'BEGIN{print num2,var1}'

#输出结果
20 hello world


awk -v num2="$num1" -v var1="$var" 'BEGIN{print num2,var1}'
#输出结果
20 hello world

vim test.awk
BEGIN{print num2,var1}
awk -v num2="$num1" -v var1="$var" -f test.awk

9. awk中数组的用法

9.1. Shell中数组的用法

array=("Allen" "Mike" "Messi" "Jerry" "Hanmeimei" "Wang")

#打印元素:      
echo ${array[2]}

#打印元素个数:  
echo ${#array[@]}

#打印元素长度:  
echo ${#array[3]}

#给元素赋值:    
array[3]="Li"

#删除元素:      
unset array[2];unset array

#分片访问:
echo ${array[@]:1:3}

#元素内容替换:
${array[@]/e/E} #只替换单个字段第一个e
${array[@]//e/E} #替换所有的e

#数组的遍历
for a in ${array[@]}
do
    echo $a
done

9.2. awk中数组的用法

在awk中,使用数组时,不仅可以使用1.2…n作为数组下标,也可以使用字符串作为数组下标。

  1. 当使用1.2.3…n时,直接使用array[2]访问元素;需要遍历数组时,使用以下形式:
str="Allen Jerry Mike Tracy Jordan Kobe Garnet"
split()
for(i=1;i<length(array);i++)
    print array[i]
  1. 当使用字符串作为数组下标时,需要使用array[str]形式访问元素;遍历数组时,使用以下形式:
array["var1"]="Jin"
array["var2"]="Hao"
array["var3"]=Fang

典型常用例子:

  1. 统计主机上所有的TCP连接状态数,按照每个TCP状态分类
#netstat -an | grep tcp |awk '{array[$6]++}END{for(a in array) print a,array[a}'
  1. 计算横向数据总和,计算纵向数据总和;
# student.txt
allen   80  90  87  91
mike    78  86  93  96
Kobe    66  92  82  78
Jerry   98  74  66  54
Wang    87  21  100 43


awk 'BEGIN{printf "%-10s%-10s%-10s%-10s%-10s\n","Name","Chinese","Math","English","Physical"}{total=$2+$3+$4+$5;printf "%-10s%-10d%-10d%-10d%-10d%-10d%-10d\n",$1,$2,$3,$4,$5,total}' student.txt

#或者
vim student.awk

BEGIN{
    printf "%-10s%-10s%-10s%-10s%-10s\n","Name","Chinese","Math","English","Physical","Total"
}

{
    total=$2+$3+$4+$5
    chinese_sum+=$2
    math_sum+=$3
    english_sum+=$4
    physical_sum+=$5
    printf "%-10s%-10d%-10d%-10d%-10d%-10d%-10d\n",$1,$2,$3,$4,$5,total
}
END {
    printf "%-10s%-10d%-10d%-10d%-10d\n","",chinese_sum,math_sum,english_sum,physical_sum
}

10. 一个复杂的awk处理生产数据的例子

需求描述:利用awk处理日志,并生成结果报告。
生成数据脚本insert.sh,内容如下:

#!/bin/bash
#

function create_random()
{
    min=$1
    max=$(($2-$min+1))
    num=$(date +%s%N)
    echo $(($num%$max+$min))
}

INDEX=1

while true
do
    for user in Allen Mike Jerry Tracy Hanmeimei Lilei
    do
        COUNT=$RANDOM
        NUM1=`create_random 1 $COUNT`
        NUM2=`expr $COUNT - $NUM`
        echo "`date '+%Y-%m-%d %H:%M:%S'` $INDEX Batches: user $user INSERT $COUNT records into database:product table:detail, Insert $NUM1 records successfully, failed $NUM2 record" >> ./db.log.`date +%Y%m%d`
        INDEX=`expr $INDEX + 1`
    done
done

数据格式如下:

2019-01-29 00:58:30 1 Batches:user allen insert 22498 records into database:product table detail,insert 20771 records successfully,failed 1727 records
2019-01-29 00:58:30 2 Batches:user mike insert 29378 records into database:product table detail,insert 21426 records successfully,failed 7952 records
2019-01-29 00:58:30 3 Batches:user jerry insert 22779 records into database:product table detail, insert 9397 records successfully, failed 13382 records
2019-01-29 00:58:30 4 Batches:user tracy insert 25232 records into database:product table detail, insert 21255 records successfully, failed 3977 records
  1. 统计每个人员分别插入了多少条record 进数据库;
#输出结果:
USER    Total_Records
allen   493082
mike    349287

vim exam_1.awk

BEGIN{
    printf "%-10s%-10s\n","User","Total Records"
}

{
    USER[$6]+=$8
}

END
{
    for(u in USER)
        printf "%-10s%-20d\n" u,USER[u]
}

#脚本文件执行命令
awk -f exam_1.awk db.log.20190129
  1. 统计每个人分别插入成功了多少record,失败了多少record;
#输出结果:
User    Success_Record  Failed_Records
jerry   3472738         283737
mike    2738237         28373

BEGIN {
    printf "%-10s%-20s%-20s\n","User","Success_Records","Failed_Records"
}

{
    SUCCESS[$6]+=$14
    FAIL[$6]+=$17
}

END {
    for(u in SUCCESS)
        printf "%-10s%-20d%-20d%-20d\n",u,SUCCESS[u],FAIL[u]
}
  1. 将例子1和例子2结合起来,一起输出,输出每个人分别插入多少数据,多少成功,多少失败,并且格式化输出,加上标题;
vim exam_3.awk

BEGIN {
    printf "%-10s%-20s%-20s%-20s\n","User","Total","Success","Failed"
}

{
    TOTAL[$6]+=$8
    SUCCESS[$6]+=$14
    FAIL[$6]+=$17
}

END {
    for(u in SUCCESS)
        printf "%-10s%-20d%-20d%-20d%-20d\n",u,TOTAL[u],SUCCESS[u],FAIL[u]
}

#脚本文件执行命令
awk -f exam_3.awk db.log.20190129
  1. 在例子3的基础上,加上结尾,统计全部插入记录数,成功记录数,失败记录数;
vim exam_4.awk

BEGIN {
    printf "%-10s%-20s%-20s%-20s\n","User","Total","Success","Failed"
}

{
    TOTAL[$6]+=$8
    SUCCESS[$6]+=$14
    FAIL[$6]+=$17
}

END {
    for(u in SUCCESS)
    {
        total+=TOTAL[u]
        success+=SUCCESS[u]
        fail+=FAIL[u]
        printf "%-10s%-20d%-20d%-20d%-20d\n",u,TOTAL[u],SUCCESS[u],FAIL[u]
    }
    
    printf "%-10s%-20d%-20d%-20d%-20d\n","",total,success,fail
}
  1. 查找丢失数据的现象,也就是成功+失败的记录数,不等于一共插入的记录数。找出这些数字并显示行号和对应行的日志信息;
awk '{if($8!=$14+$17) print NR,$0}' db.log.20190129
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值