从业务模型学习shell 之 awk

《从业务模型学习shell》系列不是一本工具书,内容为笔者在生产实践中的一些经验总结,希望以更容易理解的语言和例子让读者得到收获,快速有效应用所学习的内容提高开发工作中的效率的同时少踩到坑。若对相关操作接触较少,建议在bash中完成相关练习操作。

转载请注明出处从业务模型学习shell 之 awk

简书暂不支持TOC,本文大纲如下,有需要的可以直接跳到特定章节查看

  • 一、使用awk的目的
  • 二、正确使用awk
  • 三、业务场景案例

一、使用awk的目的

在日常生产工作中,可能经常会需要操作线上文件(如日志文件),进行一系列的统计、排序等操作,信息一般是按照指定的格式打印的,而需要进行统计加工的信息往往是希望从全局来观察了解某项指标的变化趋势。

假设你已经有了基本linux操作的经历,会知道:在linux环境下的文本处理,我们通常会使用到 tail / grep 等命令(其他章节补充介绍)来进行文本的筛查,但这两个操作很难宏观的来判断文本中某类特征的分布情况,难以帮助我们做出正确的决策。

1.1 认识awk

先来看一个简单的例子:

# 1>思考如何打印出接口的单次请求总耗时数据
# 2>思考如何统计EGetUserInfo.getUserInfo接口的平均耗时
# 文件名相对路径:info.log
# 日期时间 | 请求ID | 请求耗时 | 接口名 | 方法名 | 行号 | 日志内容
2018-02-28 19:23:03.126|CA083JDK68hB|0|wang.shikun.blog.EGetUserInfo|getUserInfo|24|in params:{"uid":103020}
2018-02-28 19:23:03.129|CA083JDK68hB|3|wang.shikun.blog.EGetUserInfo|getUserInfo|28|查询DB获取用户信息
2018-02-28 19:23:03.132|CA083JDK68hB|6|wang.shikun.blog.EGetUserInfo|getUserInfo|32|out params:{"uid":103020}
2018-02-28 19:23:04.126|CA08FCDK68hB|0|wang.shikun.blog.EGetUserInfo|getUserInfo|24|in params:{"uid":303620}
2018-02-28 19:23:04.129|CA08FCDK68hB|3|wang.shikun.blog.EGetUserInfo|getUserInfo|28|查询DB获取用户信息
2018-02-28 19:23:04.134|CA08FCDK68hB|8|wang.shikun.blog.EGetUserInfo|getUserInfo|32|out params:{"uid":303620}

想好:分析一个已知的文件,首先要搞清楚文件的结构,能够剥离出特征值,有较完整的处理思路(一般来说复杂的shell都是一步步观察输出、调试完成的,能确定正确的方向才算是已知)

在这个需求中,我们知道:以“|”分割的话,耗时这个特征值可以从日志文件的第3列拿到,这里用“out params”对应的请求耗时字段即是整个请求的执行耗时,且每次请求只打一次。(这里简单举例,具体应看实际场景)

既然是跟“第x列”相关的问题,使用 awk 再合适不过了。这里先给出第一问的shell,第2问我们在后面篇幅解答。

#例1:通过grep筛选出指定特征行后,使用awk命令打印对应列信息
grep -F 'EGetUserInfo|getUserInfo|32|out par' info.log | awk -F '|' '{print $3}'

#输出结果为:
6
8

这里, 我们指定以 “|” 作为分隔符(使用 -F 参数),再把分割后的第3列($3)打印出来。如果你手打了这条shell,但是如果把 awk 的单引号打成了双引号,会发现并没有得到你想要的结果,输出了 grep 的结果,但 awk 没有生效

#例2:awk 的错误使用
grep -F 'EGetUserInfo|getUserInfo|32|out par' info.log | awk -F '|' "{print $3}"

#输出结果为:
2018-02-28 19:23:03.132|CA083JDK68hB|6|wang.shikun.blog.EGetUserInfo|getUserInfo|32|out params:{"uid":103020}
2018-02-28 19:23:04.134|CA08FCDK68hB|8|wang.shikun.blog.EGetUserInfo|getUserInfo|32|out params:{"uid":303620}

二、正确使用awk

用好:从上面的两个例子中我们可以看到, 仅仅是一个引号的用法,就可能让我们误入歧途。所以在文本分析中,除了要有正确的处理思路,还应正确掌握工具的使用方法。

2.1 awk 的写法

通常来说,一个awk语句是这样的格式:

1)
$ awk '{ 操作语句 }' 文件名
# 注意是单引号
# 如 awk '{print $1}'  info.log      会按空格(默认)分割每一行,并输出第一列数据

2)
# 处理标准输出时
$ 标准输出 | awk '{ 操作语句}'
# 如:cat info.log | awk '{print $0}'     会将info.log文件的所有行都打印出来,这里$0是匹配整行
# 要想匹配最后一列,可以使用$NF参数

实际上, awk同其他linux命令一样,具有诸多参数,配合使用各个参数,我们可以更加灵活的获取想要的信息。在linux环境键入awk,得到的用法提示是这样的:

awk [-F fs] [-v var=value] [-f progfile | 'prog'] [file ...]

接下来,我们来看看这些参数的使用。(本文不会有较多篇幅来介绍各个参数,有需要可以查看相关手册)

2.1.1 awk -F

其中,-F 参数在前文已经提到了,指定分割符,方便我们按需要获取字符串,同时这也是我们在生产工作中使用到的最频繁的参数,因为生产实践中的文件,若使用空格来进行文本分割的话,非常容易和有效信息混淆,如这样一段日志文件,使用空格来分割每一列就显得很吃力了,想读懂都不太容易:

2018-02-28 19:23:03.126 CA083JDK68hB 0 wang.shikun.blog.EGetUserInfo getUserInfo 24 in params: {"uid": 103020}
2018-02-28 19:23:03.132 CA083JDK68hB 6 wang.shikun.blog.EGetUserInfo getUserInfo 32 out params:{"uid":103020}

当然,针对这一组还算规范的数据,我们还是可以用F参数比较轻松的获取指定信息。

# 1> 找出都有哪几行打印了日志
awk -F 'getUserInfo ' '{print $2}' info.log | awk '{print $1}'

# 输出
24
32

在本段样本数据中,我们从中选取了一段通用的可作为标准分割的字符串作为分隔符来获取数据,虽然看起来并不是很友好,但的确能帮助我们更快速的拿到目标信息又不失准确。

2.1.2 awk -v

从用法提示中我们大致能知道, -v 参数是用于定义变量。 实际上, 在处理已知文本时,需要处理的数据都是清楚的,处理逻辑也基本是基于这些文本信息,很少需要用到自定义变量来辅助我们完成样本文件的分析。

# 2> 针对2.1.1中的问题,在每一行输出前标记当前为第几行输出结果
awk -F 'getUserInfo ' '{print $2}' info.log | awk -v a=1 '{print a++":"$1}'

# 输出结果
1:24
2:32

2.2 进行统计

在掌握了基本的语法后,我们已经可以完成部分样本数据处理了。但是光把特征数据找出来是还不具有较大的统计意义,而工作中通常会需要我们基于特征数据完成部分统计需求。

2.2.1 特征行数据的累加

我们来回顾1.1中的第2问:统计接口的平均耗时。通过第一问的解答,我们已经能够拿到各次请求的总耗时,那么平均耗时就是对各次请求的耗时数据累加求平均值:

# 计算平均耗时
grep -F 'EGetUserInfo|getUserInfo|32|out par' info.log | awk -F '|' '{print $3}'  | awk '{sum += $1;a++}END{print sum/a}'

# 输出
7

这次我们在获取每次耗时的结果后新增了一段 awk '{sum += $1;a++}END{print sum/a}',这里我们使用了一个新的表达式,这是awk的流程控制表达式,在awk功能完成后(END)执行的操作,与其对应的还有BEGIN,在awk功能开始前执行的操作,我们来看具体用法:

# 计算平均耗时
# awk 'BEGIN{awk处理前逻辑}{awk逻辑}END{awk处理结束后逻辑}'
grep -F 'EGetUserInfo|getUserInfo|32|out par' info.log | awk -F '|' '{print $3}'  | awk 'BEGIN{print "平均耗时为:"} {sum += $1;a++}END{print sum/a}'

# 输出
平均耗时为:
7

在本例中,BEGIN/END 都是为了打印文本, 真正计算平均值的逻辑 {sum += $1;a++}中定义了两个局部变量sum:将每一行的第一列累加起来,a:记录“累加”这个动作执行的次数。

2.2.2 高级功能

尽管本文已经写了较长的篇幅,但相信大家读过来也发现了实际的知识点并不多,只是awk的冰山一角,甚至在其他工具书中可能只是简单的几行描述。本文更多的是阐述如何去用好awk来帮助我们解决实际问题,告诉你该怎么去思考问题的解决方案,让你知道如何使用工具书。

所谓高级功能,如其他的程序设计语言,awk也提供各种语法、函数、变量、内建变量等特性,实际上,在前文中也有所使用。通过这些特性,可以更加方便我们写出高效的shell,每一个特性都有一系列的值和定义,在这里不再粘贴,有兴趣的可以查阅awk手册 中第12至16点附录部分。若链接失效/无法打开可留言

三、业务场景案例

实际上,本文所提的内容已经涵盖了笔者工作中70%的awk操作,工作中往往不会需要特别复杂的shell脚本。但对于复杂需求,希望你通过本文的阅读,能够清楚解决方向。

3.1 大日志排查

下面我们来看一个相对复杂的需求,也是笔者最近碰到的实际工作问题,可以先思考下要怎么处理:

# 问题描述:
# 某应用在线上打印日志量巨大,打印速度惊人, 单台机器一小时打印日质量1G以上,为同类应用的5倍以上。
# 每当日志压缩、日志删除等操作都导致极大的I/O压力,期间服务性能极差,迫切需要定位出是服务代码中哪些日志在疯狂打log。

# 这里我们简化下日志样本,思路还是一样的。样本文件如下:
2018-02-28 19:23:03.126|CA083JDK68hB|0|wang.shikun.blog.EGetUserInfo|getUserInfo|24|in params:{"uid":103020}
2018-02-28 19:23:03.129|CA083JDK68hB|3|wang.shikun.blog.EGetUserInfo|getUserInfo|28|查询DB获取用户信息
2018-02-28 19:23:03.132|CA083JDK68hB|6|wang.shikun.blog.EGetUserInfo|getUserInfo|32|out params:{"uid":103020}
2018-02-28 19:23:03.134|CA553JDK68hB|6|wang.shikun.blog.EGetTotalLike|getLikeNum|98| Id:98239281sd 的文章获得198个赞赏
2018-02-28 19:23:03.134|CA553JDK68hB|6|wang.shikun.blog.EGetTotalLike|getLikeNum|98| Id:98239283sd 的文章获得98个赞赏
2018-02-28 19:23:03.134|CA553JDK68hB|6|wang.shikun.blog.EGetTotalLike|getLikeNum|98| Id:98239284dd 的文章获得18个赞赏
2018-02-28 19:23:04.126|CA08FCDK68hB|0|wang.shikun.blog.EGetUserInfo|getUserInfo|24|in params:{"uid":303620}
2018-02-28 19:23:04.129|CA08FCDK68hB|3|wang.shikun.blog.EGetUserInfo|getUserInfo|28|查询DB获取用户信息
2018-02-28 19:23:04.134|CA08FCDK68hB|8|wang.shikun.blog.EGetUserInfo|getUserInfo|32|out params:{"uid":303620}
2018-02-28 19:23:05.126|CA083JDK68hB|0|wang.shikun.blog.EGetUserInfo|getUserInfo|24|in params:{"uid":103020}
2018-02-28 19:23:05.129|CA083JDK68hB|3|wang.shikun.blog.EGetUserInfo|getUserInfo|28|查询DB获取用户信息
2018-02-28 19:23:05.132|CA083JDK68hB|6|wang.shikun.blog.EGetUserInfo|getUserInfo|32|out params:{"uid":103020}
2018-02-28 19:23:06.126|CA08FCDK68hB|0|wang.shikun.blog.EGetUserInfo|getUserInfo|24|in params:{"uid":303620}
2018-02-28 19:23:06.129|CA08FCDK68hB|3|wang.shikun.blog.EGetUserInfo|getUserInfo|28|查询DB获取用户信息
2018-02-28 19:23:06.134|CA08FCDK68hB|8|wang.shikun.blog.EGetUserInfo|getUserInfo|32|out params:{"uid":303620}
2018-02-28 19:23:07.126|CA083JDK68hB|0|wang.shikun.blog.EGetUserInfo|getUserInfo|24|in params:{"uid":103020}
2018-02-28 19:23:07.129|CA083JDK68hB|3|wang.shikun.blog.EGetUserInfo|getUserInfo|28|查询DB获取用户信息
2018-02-28 19:23:07.132|CA083JDK68hB|6|wang.shikun.blog.EGetUserInfo|getUserInfo|32|out params:{"uid":103020}
2018-02-28 19:23:08.126|CA08FCDK68hB|0|wang.shikun.blog.EGetUserInfo|getUserInfo|24|in params:{"uid":303620}
2018-02-28 19:23:08.129|CA08FCDK68hB|3|wang.shikun.blog.EGetUserInfo|getUserInfo|28|查询DB获取用户信息
2018-02-28 19:23:08.134|CA08FCDK68hB|8|wang.shikun.blog.EGetUserInfo|getUserInfo|32|out params:{"uid":303620}
2018-02-28 19:24:04.126|CA08FCDK68hB|0|wang.shikun.blog.EGetUserInfo|getUserInfo|24|in params:{"uid":303620}
2018-02-28 19:24:04.129|CA08FCDK68hB|3|wang.shikun.blog.EGetUserInfo|getUserInfo|28|查询DB获取用户信息
2018-02-28 19:24:04.134|CA08FCDK68hB|8|wang.shikun.blog.EGetUserInfo|getUserInfo|32|out params:{"uid":303620}
2018-02-28 19:24:05.126|CA083JDK68hB|0|wang.shikun.blog.EGetUserInfo|getUserInfo|24|in params:{"uid":103020}
2018-02-28 19:24:05.129|CA083JDK68hB|3|wang.shikun.blog.EGetUserInfo|getUserInfo|28|查询DB获取用户信息
2018-02-28 19:24:05.132|CA083JDK68hB|6|wang.shikun.blog.EGetUserInfo|getUserInfo|32|out params:{"uid":103020}
2018-02-28 19:24:06.126|CA08FCDK68hB|0|wang.shikun.blog.EGetUserInfo|getUserInfo|24|in params:{"uid":303620}
2018-02-28 19:24:06.129|CA08FCDK68hB|3|wang.shikun.blog.EGetUserInfo|getUserInfo|28|查询DB获取用户信息
2018-02-28 19:24:06.134|CA08FCDK68hB|8|wang.shikun.blog.EGetUserInfo|getUserInfo|32|out params:{"uid":303620}
2018-02-28 19:24:07.126|CA083JDK68hB|0|wang.shikun.blog.EGetUserInfo|getUserInfo|24|in params:{"uid":103020}
2018-02-28 19:24:07.129|CA083JDK68hB|3|wang.shikun.blog.EGetUserInfo|getUserInfo|28|查询DB获取用户信息
2018-02-28 19:24:07.132|CA083JDK68hB|6|wang.shikun.blog.EGetUserInfo|getUserInfo|32|out params:{"uid":103020}
2018-02-28 19:24:08.126|CA08FCDK68hB|0|wang.shikun.blog.EGetUserInfo|getUserInfo|24|in params:{"uid":303620}
2018-02-28 19:24:08.129|CA08FCDK68hB|3|wang.shikun.blog.EGetUserInfo|getUserInfo|28|查询DB获取用户信息
2018-02-28 19:24:08.134|CA08FCDK68hB|8|wang.shikun.blog.EGetUserInfo|getUserInfo|32|out params:{"uid":303620}
2018-02-28 19:24:03.134|CA553JDK68hB|6|wang.shikun.blog.EGetTotalRead|getReadNum|1198| Id:98239281sd 的文章获得11198个阅读量
2018-02-28 19:24:03.134|CA553JDK68hB|6|wang.shikun.blog.EGetTotalRead|getReadNum|1198| Id:98239283sd 的文章获得1098个阅读量
2018-02-28 19:24:03.134|CA553JDK68hB|6|wang.shikun.blog.EGetTotalRead|getReadNum|1198| Id:98239284dd 的文章获得1800个阅读量

这里模拟了一个日志片段, 主要问题就是:如何从中找到打印量比例最大的那些日志,改成debug或者删除。

对于这个问题, 我们首先要清楚的是,日志文件中的“接口名”、“方法名”、“行号”是我们确定代码文件的关键信息(这里称之为日志路径),而找出占比最大的日志信息就可以转变成所有日志按日志路径归类后总字符长度的占比。

3.1.1 统计每行日志长度并对应出日志路径
awk -F '|'  '{print length($0),$4"#"$5"#"$6}' info.log 
#使用到了内建函数length来统计长度
3.1.2 按“日志路径”累加字符数, 按字符数长度倒序输出
awk -F '|'  '{print length($0),$4"#"$5"#"$6}' info.log | awk '{sum[$2] += $1}END{for(row in sum ) {print sum[row], row}}' | sort -nr
# 新增部分:| awk '{sum[$2] += $1}END{for(row in sum ) {print sum[row], row}}' | sort -nr
# 使用到了数组sum、END表达式来记录每一个日志路径的累加值
3.1.3 按百分比统计并排序
 awk -F '|'  '{print length($0),$4"#"$5"#"$6}' info.log | awk '{sum[$2] += $1}END{for(row in sum ) {print sum[row], row}}' | sort -nr  | awk '{total += $1; list[$1] = $2}END{for(i in list){print 1.0*i/total*100"", i, list[i]}}' |sort -nr
# 新增部分:| awk '{total += $1; list[$1] = $2}END{for(i in list){print 1.0*i/total*100"", i, list[i]}}' |sort -nr
# 使用到了数组、累加、END表达式来计算每一个日志路径的占比情况
3.1.4 查看大于10%占比的日志量总共占的比例
 awk -F '|'  '{print length($0),$4"#"$5"#"$6}' info.log | awk '{sum[$2] += $1}END{for(row in sum ) {print sum[row], row}}' | sort -nr  | awk '{total += $1; list[$1] = $2}END{for(i in list){print 1.0*i/total*100"", i, list[i]}}' |sort -nr  | awk 'BEGIN{print "大于10%日志总占比的日志量总共占有日志文件比例:"}{if($1 > 10.0) total+=$1}END{print total"%"}'
# 新增部分:  | awk 'BEGIN{print "大于10%日志总占比的日志量总共占有日志文件比例:"}{if($1 > 10.0) total+=$1}END{print total"%"}'
# 使用到了条件语句来过滤不满足条件的值

通过以上几步的分析,我们精准的找出了日志量最大的几条日志,我们可以把重心放在优化前3条日志上了,把这3条日志打印干掉的话预估可以减少80%的日志打印量,提升服务性能。

结语

以上内容就是本次分享的awk实际应用的相关内容了,后续将对更多开发者一般不常用但是好用的linux命令进行分享,感谢关注。若文中有写错的地方,还请留言指正,防止对他人产生误导!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,552评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,666评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,519评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,180评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,205评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,344评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,781评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,449评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,635评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,467评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,515评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,217评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,775评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,851评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,084评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,637评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,204评论 2 341

推荐阅读更多精彩内容

  • linux资料总章2.1 1.0写的不好抱歉 但是2.0已经改了很多 但是错误还是无法避免 以后资料会慢慢更新 大...
    数据革命阅读 12,127评论 2 34
  • 转载 原文的排版和内容都更加友好,并且详细,我只是在这里贴出了一部分留作自己以后参考和学习,如希望更详细了解AWK...
    XKirk阅读 3,185评论 2 25
  • Ubuntu的发音 Ubuntu,源于非洲祖鲁人和科萨人的语言,发作 oo-boon-too 的音。了解发音是有意...
    萤火虫de梦阅读 99,100评论 9 467
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,714评论 6 342
  • 那年 我轻狂 喜欢上了你 那年 我懦弱 没有抓紧你 那年 我幼稚 不知道你的眼里有没有我的白衬衫 ...
    前进_1f23阅读 196评论 0 0