大家好 我是xindoo,距我上次发技术文章已经过去快一个半月了,原因是最近确实非常非常的忙,工作日除了吃饭睡觉之外,要么是在工作,要么就是在去工作的路上,而周末的时候我只想
今天1024 程序猿节,百忙中抽空发篇一直想写好久的文章来凑个热闹,简单教大家如何使用awk这个命令行工具。认识我的人都知道我最早是运维出生,做运维没学会啥太大的本事,有些命令行工具却使得贼溜,awk就是其中之一。后来我转开发后,凭借精通部分命令行工具的使用快速解决过很多小问题,命令行的便捷和高效也曾多次震惊到我们的同事们。
各种命令行工具加管道的组合,可以极快的解决很多问题,这里我就不再展开了,有兴趣可以看下我之前写的一篇博客我常用的一些linux命令。而今天的主角是awk,一款极强的文本处理工具,我日常会用它来做数据清洗、筛选、查看,甚至完成一些简单的数据统计工作。毫不夸张的说,有些别人需要几个小时、甚至完全搞不定的工作,我用awk分分钟解决完,在别人看来完全就是黑魔法。
这么说可能你没有感觉,我举个具体的例子。之前有个同事,需要把一个上千万行的文本文件(大于500MB)均匀拆成俩文件,其实就是想把千万的用户均匀随机拆成两个集合做一些对比实验,你会怎么搞? 实际上我用awk一行命令搞定,敲命令20秒,执行半分钟。
cat users.txt |awk 'NR%2==0 {print $1}' > 0.txt
cat users.txt |awk 'NR%2==1 {print $1}' > 1.txt
说这么多只是为了引出awk的强大,所以awk到底是什么?很多初学者都认为awk是一个文本处理工具,它与grep、sed同称为linux文本三剑客。 实际awk不仅仅是文本处理工具,它也是一门编程语言,awk只是针对于文本处理提供了很多内置变量和函数(稍后会详细介绍),使得他极易用于文本处理而已,接下来请跟随我由俭入深来学习下awk的使用。
基本使用
awk的基本用法就是,awk + 具体的执行 + 文本文件,它也可以从linux管道里读取内容,两种使用方法如下。
awk program textfile
cat textfile | awk program
awk实际上是面向行的数据处理的,也就是说它的指令对于每一行数据都会执行一次,比如如下的例子
cat a.txt| awk '{print $1, $3}'
上面这条指令就是输出文件所有行的第1、3列,下标是从1开始的,而$0有特殊含义,指的是这一行的所有数据。 awk缺省是使用空格或者tab来区分列的,有时候文本文件不以空格或者tab分列,而是以特殊符号(比如 - )来分列,awk也提供了-F 参数来指定分隔符。
cat a.txt| awk -F'-' '{print $1, $3}'
内置变量
awk极擅长处理文本,其中一个原因就是它提供了大量的内置变量,可以很轻易就获取到文本内容的一些信息,比如当前在第几行(NR)、这一行有多少列(NF),当前处理的文件名(FILENAME)是啥…… 下面仅列举一部分, | 变量 | 作用 |
---|---|---|
$0 | 当前行的所有内容 | |
$1~$n | 当前行的第1-第n列 | |
NF | 当前行有多少列 | |
NR | 当前是第几行,从1开始 | |
RS | 输入的记录他隔符默 认为换行符 | |
OFS | 输出字段分隔符 默认也是空格 | |
ORS | 输出的记录分隔符,默认为换行符 | |
ARGC | 命令行参数个数 | |
ARGV | 命令行参数数组 | |
FILENAME | 当前输入文件的名字 | |
IGNORECASE | 如果为真,则进行忽略大小写的匹配 | |
ARGIND | 当前被处理文件的ARGV标志符 |
比如我要输出一个文本文件a.txt,以 | 风格的话,第几行分别有多少列,我就可以这么写:
cat a.txt | awk -F'|' '{print NR, NF}'
我在博客《awk实现类sql的join操作》 中就用到了多个内置变量完成了对多个文本的复杂处理,有兴趣可以看下,类似的对多个文件求交集、差集都很容易实现。
内置函数
除了内置的变量外,awk也内置了很多常用的函数,这里我也不在赘述,具体内容可以查阅https://www.runoob.com/w3cnote/awk-built-in-functions.html ,awk内置的函数主要分为以下几种:
- 算数函数
- 字符串函数
- 时间函数
- 位操作函数
- 其它函数
这些内置函数可以完成打大多数常用的操作,如果这些内置函数还不够用的话,刚才也说过了,awk是一门编程语言,需要啥函数你都可以自己实现。
语法
接下来我们来介绍下awk在命令行外作为一门编程语言的基本知识。
变量
首先从变量开始,除了上文说到的那些内置变量,你也可以自行使用其他的变量。awk和python语言,它是弱类型的,不用声明,变量直接使用。 比如要求一个文本文件第2列的综合和平均值,就可以这么写。
cat a.txt |awk '{sum += $2; cnt += 1} END {print sum, sum/cnt}'
这里sum和cnt就是我们自定义的变量,随用随写,很是方便。除了简单变量外,awk也支持一些复杂数据结构,比如map,这里我还是举一个例子,比如我们拥有一批人最近一个月明天的体重记录,我们想知道每个人这一个月的平均体重是多少,数据如下,总共有三列,分别是姓名、日期、体重。
张三 2021-10-01 67.7
李四 2021-10-01 83.9
张三 2021-10-02 68.1
李四 2021-10-02 85.0
张三 2021-10-03 68.3
张三 2021-10-01 67.9
李四 2021-10-03 84.0
...
使用awk中的map,可以将每个人的体重总和sum和数量cnt分别存储起来,等到所有数据处理完之后统一输出即可,具体代码如下:
cat a.txt|awk '{sum[$1] += $3;cnt[$1] += 1} END {for (key in sum) {print key, sum[key]/cnt[key]}}'
判断
从上面几个例子中,大家也注意到了,有时候不得不使用一些判断条件。比如在最开始的文本拆分的例子中,我是按行号的奇偶将文件拆分成两个,这个时候需要按不同的含号执行不同的逻辑,在awk中判断逻辑也很简单。
awk 'expr { statement }' # 只有expr为true的时候大括号中的statement代码块才会执行。
像上文中已经多次出现的END就表示只有所有行都处理完后,其后面的代码块才会执行。和END对应的还有BEGIN,其所对应的代码是在文件处理开始前执行,所以一般都会做一些文件初始化的工作。 其他你自定的判断也都可以通过类似的方式写,另外它也是支持if else的,其写法如下:
cat a.txt |awk '{if (NR%2==1) print NR, $1 ; else print NR, $2}' # 如果是奇数行就输出行号和第一列,否则输出行号和第二列
循环
awk也支持for和while循环,和c语言for和while循环是一样的,如下:
for (initialisation; condition; increment/decrement)
action
while (condition)
action
这里我用awk实现输出0-100之间所有的素数为例,串一下上面说的循环和判断,除了变量定义外,和C语言基本一致了。
BEGIN {
i = 2;
while (i < 100) {
isPrime = 1;
for (j = 2; j < i; j++) {
if (i % j == 0) {
isPrime = 0;
}
}
if (isPrime == 1) {
print i;
}
i += 1;
}
}
如果代码太长,无法完整的拼接到命令行后,可以把代码存到文件中,然后用awk -f 调起,比如:
awk -f getPrime.awk
函数
awk的函数定义也非常简单,和js是一毛一样了,具体可以参考https://www.runoob.com/w3cnote/awk-user-defined-functions.html
function isPrime(n) {
for (j = 2; j < n; j++) {
if (i % j == 0) {
return 0;
}
}
return 1;
}
BEGIN {
i = 2;
while (i < 100) {
if (isPrime(i)) {
print i;
}
i += 1;
}
}
像上面的语法学过编程语言的人都不会陌生把,非常的简单。
结语
awk作为一面语言似乎非常的小众,和其他成熟编程语言比起来似乎毫无优势,但它只专注于文本处理,在文本处理这一领域却是佼佼者。不过确实也有个现象,现在随着各类分布式文本检索工具的出现(比如elastic search),会使用awk的人越来越少了,也许像这类优秀的命令行工具未来会逐渐被新生代程序猿遗忘在历史的长河中……,所以希望我这篇文章能让awk被更多的人了解到。
另外本文只是粗浅的介绍了下awk的基础功能,但如果你想要精通awk还需要自行查阅一些其他的资料,并且伴以大量的联系。今天我也听了会csdn 1024线上活动直播,恰好听到一些top级的程序猿为普通程序猿提的建议,其实都是些老生常谈的内容,道理大家都懂,但大多数人都是流于平庸,核心还是少了实践和积累。不积跬步无以至千里,不积小流无以成江河。