第五课 Shell脚本编程-awk用法

第五课 Shell脚本编程-awk用法

文章目录

  • 第五课 Shell脚本编程-awk用法
    • 第一节 awk介绍
    • 第二节 awk的内置变量
    • 第三节 awk格式化输出之printf
    • 第四节 awk的模式匹配
    • 第五节 awk动作中的表达式用法
    • 第六节 awk动作中的条件及循环语句
    • 第七节 awk中的字符串函数
    • 第八节 awk中的常用选项
    • 第九节 awk中的数组用法
    • 第十节 awk中的处理日志实例

第一节 awk介绍

  1. awk是一个文本处理工具,通常用于处理数据并生成结果报告
  2. awk的命名是它的创始人Alfred Aho .Peter Weinberger和Brian Kernighan 姓氏的首个字母组成的
  3. 语法格式
    • 第一种形式:awk 'BEGIN{}pattern{commands}END{}' file_name
    • 第二种形式:standard output |awk 'BEGIN{}pattern{commands}END{}
      第五课 Shell脚本编程-awk用法_第1张图片

第二节 awk的内置变量

第五课 Shell脚本编程-awk用法_第2张图片第五课 Shell脚本编程-awk用法_第3张图片

  1. 内置变量:
    • $0 打印行所有信息
    • 1   1~ 1 n 打印行的第1到n个字段的信息
    • NF (Number Field) 处理行的字段个数
    • NR (Number Row) 处理行的行号
    • FNR (File Number Row) 多文件处理时,每个文件单独记录行号
    • FS (Field Separator) 字段分割符,不指定时默认以空格或tab键分割
    • RS (Row Separator) 行分隔符,不指定时以回车换行分割
    • OFS (Outpput Field Separator)输出字段分隔符。
    • ORS (Outpput Row Separator) 输出行分隔符
    • FILENAME 处理文件的文件名
    • ARGC 命令行参数个数
    • ARGV 命令行参数数组
[root@localhost ~]# cat list
Hadoop Spark Flume
Java Python scala
Allen Mike Meggie
# 注意: 内置变量不用 在另外加$了
# 输出/etc/passwd 每行内容
awk '{print $0}' /etc/passwd
# 指定分隔符:打印第一列 不指定分割符默认空格非分割符
awk 'BEGIN{FS=":"}{print $1}' /etc/passwd
# 默认空格非分割符
awk '{print $1}' list
# 输出每行 根据分割符分割后 字段的个数 NF
awk '{print NF}' list
# 输出处理文件的行号 list三行 /etc/passwd从第四行开始数一直到24行
awk '{print NR}' list /etc/passwd
# 输出处理文件的行号 和上面不同 每个文件单独计数
awk '{print FNR}' list /etc/passwd

[root@localhost ~]# cat list
Hadoop|Spark:Flume
Java|Python:scala:go
Allen|Mike:Meggie
# 不同字段分割符打印出内容不同
awk '{print $1}' list
awk 'BEGIN{FS=":"}{print $1}' list
awk 'BEGIN{FS="|"}{print $1}' list
[root@localhost ~]# cat list
Hadoop|Spark:Flume--Java|Python:scala:go--Allen|Mike:Meggie
# 行分割符RS
awk 'BEGIN{RS="--"}{print $0}' list
awk 'BEGIN{RS="--";FS="|"}{print $2}' list

# 输出行分割符
awk 'BEGIN{RS="--";FS="|";ORS="@"}{print $2}' list # Spark:Flume@Python:scala:go@Mike:Meggie
# 输出字段分割符 这里字段间必须加, 才能生效
awk 'BEGIN{RS="--";FS="|";ORS="@";OFS="*"}{print $1i,$2}' list # Hadoop*Spark:Flume@Java*Python:scala:go@Allen*Mike:Meggie

# 每一行都执行 输出文件名的操作
awk '{print FILENAME}' list # list

# 每一行都执行 输出命令行参数个数 awk本身作为一个参数 文件名list也是一个参数
awk '{print ARGC}' list # 2
awk '{print ARGC}' list /etc/passwd # 3

# 打印每一行的最后一个字段 NF是每行的字段数 $NF是 最后一个字段
awk 'BEGIN{FS=":"}{print $NF}' /etc/passwd

第三节 awk格式化输出之printf

第五课 Shell脚本编程-awk用法_第4张图片

  1. 格式符
    • %s 打印字符串
    • %d 打印1o进制数
    • %f 打印浮点数
    • %x 打印16进制数
    • %o 打印8进制数
    • %e 打印数字的科学计数法格式
    • %c 打印单个字符的ASCII码
  2. 修饰符
    • - 左对齐
    • + 右对齐
    • # 显示8进制在前面加0,显示16进制在前面加0x
# print 默认回车换行
awk 'BEGIN{FS=":"}{print $1}' /etc/passwd
# printf 没有默认格式
awk 'BEGIN{FS=":"}{printf $1}' /etc/passwd
# printf 自定义回车换行
awk 'BEGIN{FS=":"}{printf "%s\n", $1}' /etc/passwd
# printf 自定义输出格式
awk 'BEGIN{FS=":"}{printf "%s %s\n", $1, $2}' /etc/passwd
# 指定变量占用的字符位数 后面空格补齐 比较美观一些 -左对齐 +右对齐
awk 'BEGIN{FS=":"}{printf "%20s %20s\n", $1, $2}' /etc/passwd
awk 'BEGIN{FS=":"}{printf "%-20s %-20s\n", $1, $2}' /etc/passwd

第四节 awk的模式匹配

  1. 模式匹配的两种用法
    • 第一种模式匹配: RegExp
    • 第二种模式匹配∶关系运算匹配
  2. RegExp
    • 匹配/etc/passwd文件行中含有root字符串的所有行
    • 匹配/etc/passwd文件行中以yarn开头的所有行
  3. 关系运算符匹配:
    • < 小于
    • > 大于
    • <= 小于等于
    • >= 大于等于
    • == 等于
    • != 不等于
    • ~ 匹配正则表达式
    • !~ 不匹配正则表达式
  4. 布尔运算符匹配:
    • ||
    • &&
    • !
# 只对符合匹配模式的行进行处理
# 匹配/etc/passwd文件行中含有root字符串的所有行
awk 'BEGIN{FS=":"}/root/{print $0}' /etc/passwd
# 匹配/etc/passwd文件行中以yarn开头的所有行
awk 'BEGIN{FS=":"}/^yarn/{print $0}' /etc/passwd

# 关系运算符
# 以:为分隔符,匹配/etc/passwd文件中第3个字段小于50的所有行信息
awk 'BEGIN{FS=":"}$3<50{print $0}' /etc/passwd
# 以:为分隔符,匹配/etc/passwd文件中第3个字段大于5的所有行信息
awk 'BEGIN{FS=":"}$3>5{print $0}' /etc/passwd
# 以:为分隔符,匹配/etc/passwd文件中第7个字段为/bin/bash的所有行信息
awk 'BEGIN{FS=":"}$7=="/bin/bash"{print $0}' /etc/passwd
# 以:为分隔符,匹配/etc/passwd文件中第7个字段不为/bin/bash的所有行信息
awk 'BEGIN{FS=":"}$7!="/bin/bash"{print $0}' /etc/passwd

# 匹配正则表达式$3~ 固定写法
# 以:为分隔符,匹配/etc/passwd中第3个字段包含3个以上数字的所有行信息
awk 'BEGIN{FS=":"}$3~/[0-9]{3,}/{print $0}' /etc/passwd
awk 'BEGIN{FS=":"}$1~/^root/{print $0}' /etc/passwd

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

第五节 awk动作中的表达式用法

第五课 Shell脚本编程-awk用法_第5张图片

awk 'BEGIN{var=20;var1="hello";print var,var1}'
# 数值运算
awk 'BEGIN{num1=20;num2+=num1;print num1,num2}'
# 浮点数运算
awk 'BEGIN{num1=20;num2=30;print num1/num2}'
awk 'BEGIN{num1=20;num2=30;printf "%0.2f\n", num1/num2}'
# 次方 ** 和 ^一样
awk 'BEGIN{num1=20;num2=3;printf "%0.2f\n", num1**num2}'
awk 'BEGIN{num1=20;num2=3;printf "%0.2f\n", num1^num2}'

awk 'BEGIN{x=20;y=x++;print x, y}'
awk 'BEGIN{x=20;y=++x;print x, y}'

# 统计空白行^$ 
awk '/^$/{sum++}END{print sum}' /etc/services

第六节 awk动作中的条件及循环语句

# if else if else
# 以:为分隔符,只打印/etc/passwd中第3个字段的数值在50-100范围内的行信息
awk 'BEGIN{FS=":"}{if($3>50 && $3 < 100) print $0}' /etc/passwd
# 以:为分隔符,只打印/etc/passwd中第3个字段的数值小于50的 信息定制输出
awk 'BEGIN{FS=":"}{if($3<50) printf "%-10s%-5d\n", "小于50的UUID", $3}' /etc/passwd
# 以:为分隔符,只打印/etc/passwd中第3个字段的数值小于50的 信息定制输出
awk 'BEGIN{FS=":"}{if($3<50) printf "%-10s%-5d\n", "小于50的UUID", $3}' /etc/passwd
# 以:为分隔符,只打印/etc/passwd中第3个字段的数值小于50的 信息定制输出 大于100 信息定制输出
awk 'BEGIN{FS=":"}{if($3<50) {printf "%-10s%-10s%-5d\n", "小于50的UUID", $1, $3} else if ($3 > 100) {printf "%-10s%-10s%-5d\n", "大于100的UUID", $1, $3}}' /etc/passwd

# 把上面命令写到文件script.awk中
BEGIN{
        FS=":"
}
{
        if($3<50){
            printf "%-10s%-10s%-5d\n", "小于50的UUID", $1, $3
        }else if ($3>50 && $3<100){
            printf "%-10s%-10s%-5d\n", "大于50小于100的UUID", $1, $3
        }else{
            printf "%-10s%-10s%-5d\n", "大于100的UUID", $1, $3
        }
}
# 引入script.awk执行
awk -f script.awk /etc/passwd

# while 和 for 和do while
# vi while.awk
BEGIN{
        while(i<=100)
        {
            sum+=i
            i++
        }
        print sum
}
# 执行
awk -f while.awk # 5050

# vi for.awk
BEGIN{
        for(i=0;i<=100;i++)
        {
            sum+=i
        }
        print sum
}
# 执行
awk -f for.awk # 5050

# vi dowhile.awk
BEGIN{
        do
        {
            sum+=i
            i++
        }while(i<=100)
        print sum
}

# 执行
awk -f dowhile.awk # 5050

# 计算下列每个同学的平均分数,并且只打印平均分数大于90的同学姓名和分数信息
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
# vi 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
       score_avg+=avg
    }

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


[root@localhost ~]# awk -f student.awk student.conf
Name      Chinese   English   Math      Physical  Average
Allen     80        90        96        98        91.00
Mike      93        98        92        91        93.50
Li        78        88        98        100       91.00
ALL       251       276       286       289       275.5

第七节 awk中的字符串函数

第五课 Shell脚本编程-awk用法_第6张图片

  1. awk中的字符串函数
    • length(str) 计算长度
    • index(str1,str2) 返回在str1中查询到的str2的位置
    • tolower(str) 小写转换
    • toupper(str) 大写转换
    • `split(str, arr, fs) 分隔字符串,并保存到数组中,默认fs空格
    • match(str, RE) 返回正则表达式匹配到的子串的位置
    • substr (str, m, n) 截取子串,从m个字符开始,截取n位。n若不指定,默认空格
    • sub(RE, Repstr, str) 替换查找到的第一个子串
    • gsub(RE, Repstr, str) 替换查找到的所有子串
# 以:为分隔符,返回/etc/passwd中每行中每个字段的长度example1.awk
BEGIN{
    FS=":"
}
{
    i=1
    while (i<=NF) {
        if (i==NF)
            printf "%d", length($i)
        else
            printf "%d:", length($i)
        i++
    }
    print ""
}
# 执行输出
awk -f example1.awk /etc/passwd

# 搜索字符串"I have a dream"中出现"ea”子串的位置
awk 'BEGIN{str="I have a dream";location=index(str,"ea");print location}'
awk 'BEGIN{str="I have a dream";location=match(str,"ea");print location}'

# 将字符串"Hadoop is a bigdata Framawork"全部转换为小写
awk 'BEGIN{str="Hadoop is a bigdata Framawork";print tolower(str)}'
# 将字符串"Hadoop is a bigdata Framawork"全部转换为大写
awk 'BEGIN{str="Hadoop is a bigdata Framawork";print toupper(str)}'

# 将字符串"Hadoop Kafka Spark Storm HDFS YARN Zookeeper",按照空格为分隔符,分隔每部分保存到数组中
awk 'BEGIN{str="Hadoop Kafka Spark Storm HDFS YARN Zookeeper";split(str, array, " "); for(a in array) print array[a]}'

# 搜索字符串"Tranction 2345 Start:Select * from master"第一个数字出现的位置
# 正则表达式必须用//
awk 'BEGIN{str="Tranction 2345 Start:Select * from maste";location=match(str,/[0-9]/);print location}'

# 截取字符串"transaction start"的子串,截取条件从第4个字符开始,截取5位
awk 'BEGIN{str="transaction start";print substr(str, 4, 5)}'

# 替换字符串"Tranction 243 start,Event ID:9002"中第一个匹配到的数字串为$符号
# 替换后str会变 str返回值为替换的个数
awk 'BEGIN{str="Tranction 243 start,Event ID:9002";print sub(/[0-9]+/, "$", str);print str}'

第八节 awk中的常用选项

第五课 Shell脚本编程-awk用法_第7张图片

  1. awk中常用选项
    • -v 定义或引用变量
    • -f 指定awk命令文件
    • -F 指定分隔符
    • -v 查看awk的版本号
num1=20
var1="qnhyn"
# 外部环境定义的变量不能直接使用 必须用-v引入到awk命令中来
# 建议加""防止出错
awk -v num1="$num1" -v var1="$var1" 'BEGIN{print num1, var1}'

# -f 和 -F之前已经用过很多次 这里就不写了
# awk -v 查看版本

第九节 awk中的数组用法

  1. she1l中数组的用法:
array=("Allen" "Mike" "Messi" "Jerry" "Hanmeimei" "wang")

# shell中数组下标是从0开始的 而awk中是从1开始的
# 打印元素: 
echo ${array[@]} # 所有元素
echo ${array[2]}
# 打印元素个数:
echo ${#array[@]}
echo ${#array[*]}
# 打印元素长度:
echo ${#array[3]}
# 给元素赋值:
array[3]="Li"
# 删除元素:
unset array[2] # 删除某个元素 这里有个坑 比如删除2 元素虽然删除了 索引没变,第二次删除2 没变化。
unset array # 清空数组
# 分片访问:
echo ${array[@]:1:3]
# 元素内容替换:
${array[@]/e/E} # 只替换第一个e
${array[@]//e/E} # 替换所有的e
# 数组的遍历:
for a in ${array[@]}
do
	echo $a
done
  1. awk中数组的用法:
    • 在awk中,使用数组时,不仅可以使用1.2…n作为数组下标,也可以使用字符串作为数组下标
# 当使用1.2.3..n做为下标时,直接使用array[2]访问元素;需要遍历数组时,使用以下形式: 
str="Allen Jerry Mike Tracy Jordan Kobe Garnet"
split(str,array)
for(i=1;i<=length(array);i++)
    print array[i]
    
awk 'BEGIN{str="Allen Jerry Mike Tracy Jordan Kobe Garnet";split(str,array);for(i=1;i<=length(array);i++) print array[i]}'
# 典型常用例子: 统计主机上所有的tcp链接状态数,按照每个tcp状态分类
netstat -an | grep tcp | awk '{arr[$6]++}END{for (i in arr) print i,arr[i]}'

# 当使用字符串作为数组下标时,需要使用array[str]形式访问元素;遍历数组时,使用以下形式:
array["var1"]="Jin"
array["var2"]="Hao"
array["var3"]="Fang"

for(a in array)           # 推荐使用这种方法遍历数组
	print array[a]        # 字符串数组下标只能用这种方式遍历 

awk 'BEGIN{array["var1"]="Jin";array["var2"]="Hao";array["var3"]="Fang";for(a in array) print array[a]}'

第十节 awk中的处理日志实例

  1. 模拟数据db.log
2019-06-08 10:31:40 15459 Batches: user Jerry insert 5504 records into datebase:product table:detail, insert 5253 records successfully,failed 251 records
2019-06-08 10:31:40 15460 Batches: user Tracy insert 25114 records into datebase:product table:detail, insert 13340 records successfully,failed 11774 records
2019-06-08 10:31:40 15461 Batches: user Hanmeimei insert 13840 records into datebase:product table:detail, insert 5108 records successfully,failed 8732 records
2019-06-08 10:31:40 15462 Batches: user Lilei insert 32691 records into datebase:product table:detail, insert 5780 records successfully,failed 26911 records
2019-06-08 10:31:40 15463 Batches: user Allen insert 25902 records into datebase:product table:detail, insert 14027 records successfully,failed 11875 records
  1. 数据文件处理
# 案例一:统计每个人分别插入了多少条record进数据库
# exam1.awk
BEGIN{
    printf "%-20s%-20s\n","User","Total records"
}
 
{
    USER[$6]+=$8
}
 
END{
    for(u in USER)
        printf "%-20s%-20d\n",u,USER[u]
}

# 案例二:统计每个人分别插入成功了多少record,失败了多少record exam2.awk
BEGIN{
    printf "%-30s%-30s%-30s\n","User","Success records","Failed records"
}
 
{
    SUCCESS[$6]+=$14
    FAILED[$6]+=$17
}
 
END{
    for(u in SUCCESS)
        printf "%-30s%-30d%-30d\n",u,SUCCESS[u],FAILED[u]
}

# 案例三: 将例子1和例子2结合起来,一起输出,输出每个人分别插入多少条数据,多少成功,多少失败,并且要格式化输出,加上标题  exam3.awk
BEGIN{
    printf "%-30s%-30s%-30s%-30s\n","Name","total records","success records","failed records"
}
 
{
    TOTAL_RECORDS[$6]+=$8
    SUCCESS[$6]+=$14
    FAILED[$6]+=$17
}
 
END{
    for(u in TOTAL_RECORDS)
        printf "%-30s%-30d%-30d%-30d\n",u,TOTAL_RECORDS[u],SUCCESS[u],FAILED[u]
}

# 案例四:在例子3的基础上,加上结尾,统计全部插入记录数,成功记录数,失败记录数 exam4_b.awk
BEGIN{
    printf "%-30s%-30s%-30s%-30s\n","Name","total records","success records","failed records"
}
 
{
    TOTAL_RECORDS[$6]+=$8
    SUCCESS[$6]+=$14
    FAILED[$6]+=$17
}
 
END{
    for(u in TOTAL_RECORDS)
    {
        # 在统计出的结果数组中进行累加
        records_sum+=TOTAL_RECORDS[u]
        success_sum+=SUCCESS[u]
        failed_sum+=FAILED[u]
        printf "%-30s%-30d%-30d%-30d\n",u,TOTAL_RECORDS[u],SUCCESS[u],FAILED[u]
    }
 
    printf "%-30s%-30d%-30d%-30d\n","",records_sum,success_sum,failed_sum
}

# 案例五: 查找丢失数据的现象,也就是成功+失败的记录数不等于一共插入的记录数,找出这些数据并显示行号和对应行的日志信息 exam5.awk
BEGIN{
}
{
    if($8!=$14+$17)
    print NR,$0
}

# 执行
awk -f exam.awk db.log

你可能感兴趣的:(linux)