awk学习笔记

一. 概述

awk 是一个强大的文本分析工具.他的工作流程是按行读入文本数据,然后默认以空格分隔切分行数据.然后切分开的部分按照指定的语句进行处理.

其中我们常用的应该是gawk.他是GNU版本的AWK实现.相当于就是一种工具.然后我第一次写是用的mawk,他相当于是awk语言的解释器.这两者我在做题时仅仅遇到了一处不一样的地方,就是对二维数组的支持.

gawk应该是完全支持的,格式如下即可使用:

array_name[i][j]

在mawk中则是以另外一种形式支持二维数组:

array_name[i,j]

这边暂时还是以gawk为主.

二. awk基本说明

这边我个人使用过程中的一个模板是这样的:

awk [-F filed-separator] 'pattern{command}' file-source

其中 -F 指定分隔符,默认是空格.pattern是匹配原则.{}中是处理命令,最后则是数据源

有两个特殊的pattern:BEGINEND.

1
BEGIN{command}

BEGIN是指在读入数据之前匹配成功,所以BEGIN后接的指令是在读取数据之前执行.可以用来做变量声明以及初始化等等

1
END{command}

END是指处理完文件后匹配成功.所以后面接的指令是在读取数据完成之后执行的,可用来清理现场或者处理结果

然后有部分内置变量说明下(详细的可去官网查看):

NR 已读取的记录数

NF 记录域的个数

FILENAME 浏览的文件名

针对NF,其中 $n是用来获取对应域的数据的.不过比较不同的是下标从1开始,0表示的是该行全部数据.

一个最简单的例子:
文件file.txt如下

1
2
3
name age
evan 12
gkond 13
1
awk '/va/{print $2}' file.txt

这个就是当读入的数据能和va匹配时就输出第二个域.这里只有evan中含有va,所以输出的就是12.

其中awk是支持编程的,整体语句规范类似C.

三. 实例

3.1 转置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
给定一个文件 file.txt,转置它的内容。
你可以假设每行列数相同,并且每个字段由 ' ' 分隔.
示例:
假设 file.txt 文件内容如下:
name age
alice 21
ryan 30
应当输出:
name alice ryan
age 21 30

这个最直观的一个想法就是创建一个二维数组res[NF][NR],然后按照每个域输出数据即可.

所以具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
awk 'BEGIN{
col=0;row=0;
}
{
for(i=1;i<=NF;i++){
res[NR,i]=$i
}
col=NF;
row=NR
}END{
for(i=1;i<=col;i++){
for(j=1;j<=row;j++){
if(j!=row){
printf("%s ",res[j,i])
}else{
printf("%s\n",res[j,i])
}
}
}
}' file.txt

其中如果使用的是gawk的话,res[j,i]的格式可以写成res[j][i]

上面的代码是为了说明二维数组的使用的,其实还有一种就是一维数组直接存储拼接好的字符串,效率也会更高,代码如下:

1
2
3
4
5
6
7
8
9
10
awk '{
for (i = 1; i <= NF; ++i) {
if (NR == 1) s[i] = $i;
else s[i] = s[i] " " $i;
}
} END {
for (i = 1; s[i] != ""; ++i) {
print s[i];
}
}' file.txt

这里代码的意思就是每行进来时,遍历每个域,第i个域就拼到s[i]中,如果是第一行就直接拼接,如果不是第一行就得多加个空格拼接.处理完成之后就是输出了.

3.2 统计词频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
写一个 bash 脚本以统计一个文本文件 words.txt 中每个单词出现的频率。
为了简单起见,你可以假设:
words.txt只包括小写字母和 ' ' 。
每个单词只由小写字母组成。
单词间由一个或多个空格字符分隔。
示例:
假设 words.txt 内容如下:
the day is sunny the the
the sunny is is
你的脚本应当输出(以词频降序排列):
the 4
is 3
sunny 2
day 1
说明:
不要担心词频相同的单词的排序问题,每个单词出现的频率都是唯一的。
你可以使用一行 Unix pipes 实现吗?

这里我看到的最佳答案是通过pipe拼接多个命令完成的:

1
cat words.txt | tr -s ' ' '\n' | sort | uniq -c | sort -r | awk '{print $2" "$1}'

这里代码的意思是:获取文件内容,然后交给tr做字符替换,空格转换为换行,接着升序排序,再交给uniq做去重并统计,统计的结果在交给sort处理成降序,最后交给awk按照第二个域 第一个域的格式输出.

当然也可以将大部分操作交给awk做:

1
2
3
4
5
6
7
8
9
awk '{
for(i=1;i<=NF;i++){
res[$i]+=1;
}
}END{
for(i in res){
print i " " res[i]
}
}' words.txt | sort -nr -k 2

这里就是用单词做下标,次数做value来存储数据,然后按照第二个pos倒序排列.

客官扫码领红包哟~