前言

之前针对WorkerHub小程序做的数据分析文章 互联网卷王花落谁家? 收到了一些小伙伴的回复,点名要学习数据分析,其实我也是只知其一;不知其二,想着来写几篇文章简略聊下我剖析的过程。

首先是数据荡涤和统计了,这块我并没有用诸如Python之类的脚本语言,尽管Python提供了很多弱小的数据分析库如Pandas、Numpy等,然而毕竟要麻烦一些,还要写个脚本,装一堆库(PS:其实就是懒)。

我用的是一个老的Unix工具 AWK,尽管历史比拟长远,然而它简洁和丰盛的性能能够称之为神器,同时也是后盾同学必须要把握的一个工具,毕竟通过日志紧急定位线上问题的时候,你不能跟老板说:等等我先写个脚本,那老板原地暴毙了。

利用场景

AWK是1977年贝尔实验室的三个兄弟( Alfred Aho、Peter Weinberger、 Brian Kernighan )搞进去的文本剖析工具,这三个哥们的首字母拼起来就是AWK的名字了。

AWK解决文本就像其余语言解决数值一样不便,所以常常被利用在文本处理畛域。

比方日志剖析、数据荡涤、文本过滤、数据统计等。

同时AWK也是一门编程语言,不过它的命令行用法就能够满足大多数的利用场景。

咱们通常能够应用一行AWK命令实现一个脚本的工作!!!

AWK所实用的文本处理通常都有一些独特&显著的特点:

1. 输出数据格式对立

比方日志,为了对日志进行上报、监控、统计分析,咱们通常会采纳一些宰割伎俩来记录日志 (或者json等易于统计的格局)。

例如如下日志采纳"|"来宰割日志。

# 日志格局:{服务}|{日期}|{业务}|{申请URL}|{返回状态}|{申请耗时}|{申请参数}|{返回参数}...

比方CSV文件,采纳","来宰割。

# CSV格局:field1,field2,field3...

如果输出数据不是固定格局,通常会应用sed、grep等工具来过滤、荡涤为awk能够解决的模式。

2. 每一列代表固定含意,便于数据分析

输出文件每一行的雷同列类型统一,如果每一列含意不同,那就失去了数据分析的意义。

比方本文的第一个演示数据,第一列示意地区,第二列示意总人口等。

演示数据来源于国家统计局。

各地区户口注销地在外乡镇街道的人口情况

因为演示数据文件行数太多占用篇幅较长,以下演示均只展现前几条数据。

$ cat population.txt|head -n 10地区      共计      本县/市/区  本省其余县/市/区     省外全国    260937942   90372599     84689006       85876337北京    10498288    1582574      1871181        7044533天津    4952225     1095282      865442         2991501河北    8297279     4263957      2628649        1404673山西    6764665     3643627      2189385        931653内蒙古  7170889     2732591      2994117        1444181辽宁    9310058     3899728      3623800        1786530吉林    4462177     2604239      1401439        456499黑龙江  5557828     2800727      2250704         506397

Let's start !!!

根本用法

一个AWK程序的组成非常简单,它的核心内容是:一个或多个 "模式–动作" 语句序列。

"模式–动作" 序列用单引号包起来,动作放在花括号里,再传入输出文件即可。

### 一个 模式-动作awk 'pattern {action}' input_files### 多个 模式-动作awk 'pattern1 {action1} pattern2 {action2} pattern3 {action3} ...' input_files

AWK会每次读取一个输出行,对读取到的每一行,按程序查看每一个模式。

如果以后行合乎模式,则执行对应动作。

所以AWK的工作原理就是按程序匹配模式而后执行动作。

能够设想到AWK伪代码大略长这样,我猜的(*^_^*)。

### AWK伪代码  我猜的 (*^_^*)while(getline(inputfile)){    if(模式1 == true)    {        动作1;    }    if(模式2 == true)    {        动作2;    }    ....}

AWK在主动扫描输出文件的同时, 也会依照分隔符(默认空格/Tab)把每一个输出行切分成字段。

其中 $0 示意整行,$1,$2...$n 别离示意第一列,第二列...第N列。

大抵的流程图如下:

大部分的工作都是AWK主动实现的:包含按行输出,字段宰割,字段存储等。

所以咱们只须要给出 "模式–动作" 序列就能够实现对文件的操作!!!

来个 Hello World 吧,输入 "hello" 和 整行 ($0)。

print 函数应用逗号分隔不同的参数,打印后果用空格符分隔,并且会主动换行。(相似于各大语言println函数)。

模式能够省略,示意匹配所有行。

$ awk '{print "hello",$0}' population.txt|head -n 5hello 地区      共计      本县/市/区  本省其余县/市/区     省外hello 全国    260937942   90372599     84689006       85876337hello 北京    10498288    1582574      1871181        7044533hello 天津    4952225     1095282      865442         2991501hello 河北    8297279     4263957      2628649        1404673

AWK提供了很多有用的内置变量,如:

NR (Number Of Record) :示意读取到的记录数,即以后行号。

FILENAME :示意以后输出的文件名。

NF (Number Of Field) :示意以后记录的字段个数,即总共多少列。

咱们通常用 $NF 提取以后行的最初一列。

如下例子所示,总共有5列,$NF代表的就是第五列的值,等价于$5,$(NF-1)示意倒数第二列的值。

$ awk '{print FILENAME,NR,$1,$3,NF,$NF}' population.txt|head -n 5population.txt 1 地区 本县/市/区 5 省外population.txt 2 全国 90372599 5 85876337population.txt 3 北京 1582574 5 7044533population.txt 4 天津 1095282 5 2991501population.txt 5 河北 4263957 5 1404673

常见的内建变量能够去附录查阅:常见的内建变量 。

AWK也提供了格式化输入函数,跟C语言的printf用法一样。

$ awk '{printf "%s的当地总人口有:%d,省外人口有:%0.2f\n",$1,$2,$NF}' population.txt|tail -n 5陕西的当地总人口有:5894416,省外人口有:974362.00甘肃的当地总人口有:3112722,省外人口有:432833.00青海的当地总人口有:1140954,省外人口有:318435.00宁夏的当地总人口有:1534482,省外人口有:368451.00新疆的当地总人口有:4276951,省外人口有:1791642.00

格式化规定能够参考:https://www.gnu.org/software/... 。

模式过滤

下面介绍了动作的应用,动作通常用来输入展现。

模式用来过滤咱们想要的记录。

如下筛选(行号>1 且 第二列大于11074525)的行。

### AWK的变量也能够自在进行算术运算(加减乘除),比方 $2-$3$ awk 'NR>1 && $2>11074525 {print NR,$1,$2,$2-$3}' population.txt2 全国 260937942 17056534311 上海 12685316 1101602912 江苏 18226819 1368178913 浙江 19900863 1527403217 山东 13698321 712353021 广东 36806649 3139043725 四川 11735152 6913850

AWK的字符串拼接跟shell一样简略粗犷,不须要应用任何运算符。

将两个字符串并排放在一起就能实现拼接。

$ awk 'NR>1 {print NR,"开始_"$1"_完结"}' population.txt|head -n 52 开始_全国_完结3 开始_北京_完结4 开始_天津_完结5 开始_河北_完结6 开始_山西_完结

AWK还提供了很多有用的内置函数。

length(s):用来计算字符串s 的长度。

### 我的零碎编码 & 文件编码均为UTF-8$ awk 'length($1) > 6 {print $1,"占用长度:",length($1)}' population.txt内蒙古 占用长度: 9黑龙江 占用长度: 9

substr(s,p):求字符串s的子串,从地位p开始到开端。

$ awk '{print $1,substr($1,4)}' population.txt|head -n 5地区 区全国 国北京 京天津 津河北 北

常见的内建函数能够去附录查阅:常见的内建函数 。

AWK还提供了一些非凡的模式,比方 BEGIN 和 END。这两个模式不匹配任何输出行。

当 awk读取数据前,BEGIN 的语句开始执行,通常用于初始化。

例如咱们能够用BEGIN来给输入打印一个表头。

### 多个 "模式-动作" 并排写就行。$ awk 'BEGIN{print "AREA TOTAL LOCAL OTHER OUTLAND"} NR>2{print}' population.txt|head -n 5AREA TOTAL LOCAL OTHER OUTLAND北京    10498288    1582574      1871181        7044533天津    4952225     1095282      865442         2991501河北    8297279     4263957      2628649        1404673山西    6764665     3643627      2189385        931653

当所有输出行被处理完毕,END 的语句开始执行。通常用来收尾。

例如咱们能够统计一下第二列大于262005的省份,并在END进行打印。

$ awk 'NR>2 && $2>262005{count += 1} END{print count"个大于262005的省份"}' population.txt30个大于262005的省份

同一个动作里的多个语句之间应用分号或者换行进行宰割。

如下在BEGIN的动作中先指定输入分隔符,接着打印表头。

OFS (Output Formmat Separate) 也是一个内建变量:指定输入字段宰割符。

如下指定输入时字段采纳逗号进行宰割。

$ awk 'BEGIN{OFS=",";print "AREA,TOTAL,LOCAL,OTHER,OUTLAND"} NR>2{print $1,$2,$3,$4,$5}' population.txt|head -n 5AREA,TOTAL,LOCAL,OTHER,OUTLAND北京,10498288,1582574,1871181,7044533天津,4952225,1095282,865442,2991501河北,8297279,4263957,2628649,1404673山西,6764665,3643627,2189385,931653

AWK提供了范畴模式能够依据一个区间来匹配多个输出行。

范畴模式由两个被逗号离开的模式组成。

awk 'pattern1,pattern2 {action}' input_file

AWK从合乎 pattern1 的行开始,到合乎 pattern2 的行完结 (包含这两行),对这其中的每一行执行action。

如下提取第五行到第十行之间地区的数据。

$ awk 'NR==5,NR==10" {print NR,$0}' population.txt5 河北    8297279     4263957      2628649        14046736 山西    6764665     3643627      2189385        9316537 内蒙古  7170889     2732591      2994117        14441818 辽宁    9310058     3899728      3623800        17865309 吉林    4462177     2604239      1401439        45649910 黑龙江  5557828     2800727      2250704        506397

流程管制

前文提到了AWK也是一门编程语言,所以它反对很多编程语言个性,与C语言应用相似。

比方流程管制语句 if-else 、循环(for,while)。

比方数据结构数组等。

它们只能用在动作里。

如下示例应用if-else统计第二列大于4462177 和小于4462177的别离有多少行。

$ awk 'NR>2{if($2>4462177) more+=1; else less+=1} END{print "more:",more,"less:",less}' population.txtmore: 24 less: 7

下面这个例子也能够拆分成多个"模式-动作"来实现。

$ awk 'NR>2 && $2>4462177{more+=1} NR>2 && $2<=4462177{less+=1} END{print "more:",more,"less:",less}' population.txtmore: 24 less: 7

再来看个for循环的例子,打印AWK的命令行参数。

命令行参数在输出文件后追加就能够传入。

$ awk 'BEGIN {for(i=0;i<ARGC;i++) printf "%s\t",ARGV[i]; print ""}' population.txt abc def cdgawk    population.txt    abc    def    cdg

ARGC和ARGV也是AWK的内建变量,跟C语言的参数构造差不多。

ARGC:命令行参数的个数。

ARGV:命令行参数数组。

// 等价于C语言int main(int argc, char *argv[])

AWK也反对应用数组进行数据存储。

如下示例将对输出行进行倒序输入。

$ awk '{addr[NR]=$1} END{i=NR; while(i>0){print i,addr[i];i-=1}}' population.txt|head -n 533 新疆32 宁夏31 青海30 甘肃29 陕西

正则表达式

AWK 提供了对正则表达式的反对,正则表达式放在一对斜杠里:/regexpr/ 。

AWK应用 "\~" 符号示意字符串匹配,"!\~" 符号示意不匹配。

所以咱们能够在模式中判断一个字符串是否匹配一个正则表达式。

如下示例对 第一列含有 “北” 且第二列不蕴含 “88” 的行 进行打印。

$ awk '$1 ~ /北/ {print}' population.txt北京    10498288    1582574      1871181        7044533河北    8297279     4263957      2628649        1404673湖北    9250228     4445565      3791051        1013612$ awk '$1 ~ /北/ && $2 !~ /88/ {print}' population.txt河北    8297279     4263957      2628649        1404673湖北    9250228     4445565      3791051        1013612

如果判断整行是否匹配,能够省略 "~" 的左值,如下所示。

###  /regexpr/ 等价于 $0  ~ /regexpr/### !/regexpr/ 等价于 $0 !~ /regexpr/$ awk '!/西/ && /88/ {print}' population.txt北京    10498288    1582574      1871181        7044533内蒙古  7170889     2732591      2994117         1444181福建    11074525    3162036      3598887        4313602湖南    7898815     4170436      3003397        724982海南    1843430     586432       668535         588463青海    1140954     351988       470531         318435

正则表达式的语法细节本文不过多阐明。

以下是几个小例子能够参考:

### 匹配小写字母结尾的字符串$ awk '/^[a-z]/' <<< "`echo -e "apple333\n1999fds\nhaode3232\n4343...\nhaoya328"`"apple333haode3232haoya328### 验证是否是11位国内手机号码$ awk '/^1[3584][0-9]{9}$/' <<< "`echo -e "18894465939\n1364483882\n13644838825\n23443243432\n1334funny"`"1889446593913644838825

进阶用法

接下来换个内容丰盛的数据集来演示。

以下是 豆瓣电影评分Top250 的 CSV数据集。

### 数据格式:排行,电影名,评分,年份,导演,标签,星级$ cat douban_top250.csv|head -n 5rank,title,rating_num,year,director,quote,star1,肖申克的救赎,9.7,1994,弗兰克·德拉邦特 Frank Darabont,心愿让人自在。,23045692,霸王别姬,9.6,1993,陈凯歌 Kaige Chen,风华绝代。,17098203,阿甘正传,9.5,1994,罗伯特·泽米吉斯 Robert Zemeckis,一部美国近现代史。,17331124,这个杀手不太冷,9.4,1994,吕克·贝松 Luc Besson,怪蜀黍和小萝莉不得不说的故事。,1913405

AWK默认依照 空格/Tab 对每一个输出行进行切分。

咱们能够应用 -F 参数进行指定分隔符,也反对多个分隔符。

### 指定分隔符$ awk -F',' '{print $1,$2,$3}' douban_top250.csv|head -n 3rank title rating_num1 肖申克的救赎 9.72 霸王别姬 9.6### 多个分隔符 能够看到评分被切分了$ awk -F'[,.]' '{print $1,$2,$3,$4}' douban_top250.csv|head -n 3rank title rating_num year1 肖申克的救赎 9 72 霸王别姬 9 6

AWK反对应用shell重定向运算符 > 和 >> ,能够对文件进行拆分。

如下将 评分9以上的另存为douban_more_9.csv,评分9以下的为douban_less_9.csv。

$ awk -F',' 'NR>1 && $3>=9 {print $0 > "douban_more_9.csv"} NR >1 && $3<9 {print $0 > "douban_less_9.csv"}' douban_top250.csv$ cat douban_less_9.csv|head -n 561,让子弹飞,8.9,2010,姜文 Wen Jiang,你给我翻译翻译,神马叫做TMD的惊喜。,129484563,绿皮书,8.9,2018,彼得·法雷里 Peter Farrelly,去除偏见,须要勇气。,124516065,本杰明·巴顿奇事,8.9,2008,大卫·芬奇 David Fincher,在工夫之河里感触溺水之苦。,78881568,看不见的客人,8.8,2016,奥里奥尔·保罗 Oriol Paulo,你认为你认为的就是你认为的。,96503869,西西里的漂亮传说,8.9,2000,朱塞佩·托纳多雷 Giuseppe Tornatore,漂亮无罪。,781719$ cat douban_more_9.csv|head -n 51,肖申克的救赎,9.7,1994,弗兰克·德拉邦特 Frank Darabont,心愿让人自在。,23045692,霸王别姬,9.6,1993,陈凯歌 Kaige Chen,风华绝代。,17098203,阿甘正传,9.5,1994,罗伯特·泽米吉斯 Robert Zemeckis,一部美国近现代史。,17331124,这个杀手不太冷,9.4,1994,吕克·贝松 Luc Besson,怪蜀黍和小萝莉不得不说的故事。,19134055,泰坦尼克号,9.4,1997,詹姆斯·卡梅隆 James Cameron,失去的才是永恒的。,1695453

AWK也反对三目表达式,下面语句等价于上面。

$ awk -F',' 'NR>1 {print $0 > ($3>=9 ? "douban_more_9.csv":"douban_less_9.csv")}' douban_top250.csv

同时咱们能够对文件进行批量解决。

比方上面提取第二列和最初一列进行MySQL入库。

这在数据量大的时候很管用。

比方几万、几亿的数据能够疾速转化为SQL语句。

### 留神 双引号只须要斜杠本义:\"### 单引号除了斜杠本义还要用''包围起来: '\''$ awk -F',' 'NR>1 {print "insert into `movie` (name,star) values ('\''"$2"'\'','\''"$NF"'\'');" > "movie.sql"}' douban_top250.csvcat movie.sql|head -n 5insert into `movie` (name,star) values ('肖申克的救赎','2304569');insert into `movie` (name,star) values ('霸王别姬','1709820');insert into `movie` (name,star) values ('阿甘正传','1733112');insert into `movie` (name,star) values ('这个杀手不太冷','1913405');insert into `movie` (name,star) values ('泰坦尼克号','1695453');

统计Top250里各个评分所占数量。

$ awk -F',' 'NR>1{count[$3]++} END{for(i in count) print "豆瓣电影Top250里评分",i,"的电影有",count[i],"个"}' douban_top250.csv豆瓣电影Top250里评分 9.0 的电影有 20 个豆瓣电影Top250里评分 9.1 的电影有 23 个豆瓣电影Top250里评分 9.2 的电影有 19 个豆瓣电影Top250里评分 9.3 的电影有 17 个豆瓣电影Top250里评分 9.4 的电影有 6 个豆瓣电影Top250里评分 9.5 的电影有 4 个豆瓣电影Top250里评分 9.6 的电影有 2 个豆瓣电影Top250里评分 9.7 的电影有 1 个豆瓣电影Top250里评分 8.3 的电影有 1 个豆瓣电影Top250里评分 8.4 的电影有 3 个豆瓣电影Top250里评分 8.5 的电影有 11 个豆瓣电影Top250里评分 8.6 的电影有 25 个豆瓣电影Top250里评分 8.7 的电影有 42 个豆瓣电影Top250里评分 8.8 的电影有 38 个豆瓣电影Top250里评分 8.9 的电影有 38 个

找出Top250里拍过多个电影的导演。

$ awk -F',' 'NR>1{print $5}' douban_top250.csv|sort|uniq -c|sort -rn|head -n 5   8 宫崎骏 Hayao Miyazaki   7 克里斯托弗·诺兰 Christopher Nolan   6 史蒂文·斯皮尔伯格 Steven Spielberg   5 王家卫 Kar Wai Wong   5 李安 Ang Lee

找出Top250里即拍过评分9以上 又拍过9分以下的导演。

即求 douban_less_9.csv 和 douban_more_9.csv 两个文件的交加。

$ awk -F',' 'NR==FNR{map[$5]++} NR>FNR{if($5 in map)print $5}' douban_less_9.csv douban_more_9.csv|sort|uniq -c   1 Chris Columbus   2 李安 Ang Lee   1 姜文 Wen Jiang   1 大卫·芬奇 David Fincher   1 罗伯·莱纳 Rob Reiner   1 刘伟强 / 麦兆辉   1 黑泽明 Akira Kurosawa   1 杨德昌 Edward Yang   4 宫崎骏 Hayao Miyazaki   2 刘镇伟 Jeffrey Lau   1 詹姆斯·卡梅隆 James Cameron   2 朱塞佩·托纳多雷 Giuseppe Tornatore   3 史蒂文·斯皮尔伯格 Steven Spielberg   1 是枝裕和 Hirokazu Koreeda   2 弗朗西斯·福特·科波拉 Francis Ford Coppola   3 克里斯托弗·诺兰 Christopher Nolan

数组的key能够字符串拼接,这样能够间接实现二维数组的逻辑。

$ awk -F',' 'NR==2,NR==5{a[$1"-"$2]=$3} END {for (i in a) print i, a[i]}' douban_top250.csv1-肖申克的救赎 9.73-阿甘正传 9.54-这个杀手不太冷 9.42-霸王别姬 9.6

数据统计的大部分需要都能够用AWK疾速的实现。

比方:过滤、统计、聚合、并集、交加、差集等。

快来试试吧!!!

本文所有用到的数据集能够在奇观狗狗后盾回复:"awk" 进行获取

附录

常见的内建变量

内建变量补充默认值 含意
NF以后记录的字段个数,即总共多少列
NR读取到的记录数,即以后行号
FNR以后输出文件的记录个数,区别于NR,NR示意整体的记录数,FNR示意以后文件
ARGC命令行参数的个数
ARGV命令行参数数组
FS指定输出行的字段宰割符
FILENAME以后输出文件名
OFS指定输入字段宰割符
ORS指定输入的记录宰割符 默认是换行 "\n"
RS指定输出行的记录宰割符 默认是换行 "\n"

常见的内建函数

函数含意
length(s)字符串s长度
tolower(s)把字符串转为小写
substr(s, p)字符串s的子串,从地位p开始到开端
split(s, a, fs)把字符串s依据fs进行宰割,存到数组a中
sprintf(fmt,expr-list)跟C语言sprintf一样,用于字符串格式化
int(x)取x 的整数局部
sin(x) / cos(x) / sqrt(x)正弦 / 余弦 / 平方根
rand()随机数 配合 srand(x)应用 x 是 rand() 的随机数种子
match(s,r)正则表达式匹配,测试 s 是否蕴含能被 r 匹配的子串
sub(r,s)正则表达式替换,将 $0 的第一个被r匹配的子串替换为s
gsub(r,s)正则表达式全局替换,将 $0 中所有被r匹配的子串替换为s

对于咱们

欢送关注公众号《奇观狗狗》,很开心在这里能和你相遇~

咱们会分享一些技术文章,包含但不限于游戏技术、云原生、ACM题解、根底编程常识等,如果能授人以渔,荣幸之至!

咱们也会做一些有温度的产品、游戏,会陆续分享给大家,如果能博君一笑,再好不过!

产品列表:
★ WorkerHub小程序,信息均来自各个大厂员工爆料,能够查问各个公司/部门/岗位的工作做细、工作体验、工作评估等,供打工er找工作的时候参考,避雷卷王团队/天坑团队!