Aha awk!
title: Aha awk!
date: 2019-01-04 18:55:02
tags: [tools]
原文链接
在开始学Linux的时候就听说过awk这个工具,但当时觉得,这个工具,好像平时用不太到啊,于是就没怎么学。不过最近写脚本的时候,在网上看到了一些awk的脚本,惊叹于awk的文本处理能力,我想,是时候拿起awk这个文本处理神器了。
介绍
awk其实是Aho,Winberger和Kernighan三位大牛的首字母组合而成的。它既可以指awk这个可执行文件,也可以指awk这种语言,嗯,没错awk是一种语言!
Hello
先来个简单的程序,在终端输入下面代码:
$ awk -F: '$1 == "root" {print $0}' /etc/passwd
解释一下上面的这个命令,-F:
指的是分割符为:
,后面字符串的意思是,如果被分隔符分隔的第一项为root
,那就输出这一行到标准输出。而/etc/passwd是awk的处理的文本。
输出结果为:
root:x:0:0:root:/root:/bin/bash
语言
下面讲解一下awk的基本语法,其实awk的语法和C的语法有相似之处,如果不知道怎么用或者忘了,大可以用C的语法试试。
当然,如果忘了awk语法或者某个函数的用法,man吧,特别详细。
模式与行为
从上面这个命令中我们可以窥见awk语法的大致组成,它是有模式和行为组成的。
对于每一行,如果前面的模式匹配了,那么条件后面紧跟的行为就会被执行。这个模式可以是类似于上面$1 == "root"
的逻辑表达式,也可以是正则表达式。
上面的一行命令其实可以写成单独的脚本存储为a.awk:
BEGIN {
FS=":" #设定分隔符
}
$1 == "root" {
print $0
}
然后使用命令awk -f a.awk /etc/passwd
,执行效果是一样的。
模式
awk中的模式有如下几种(man awk):
BEGIN
END
BEGINFILE
ENDFILE
/regular expression/
relational expression #也就是$1 == "root"这种,关系表达式
pattern && pattern
pattern || pattern
pattern ? pattern : pattern #三目!awk强啊
(pattern)
! pattern
pattern1, pattern2 #这将会匹配到pattern1到pattern2之间的内容,是一种range pattern,如果忘了,man
正则模式示例,显示当前目录下,大小的单位为K的文件。
$ ls -lh | awk '/([0-9]+\.)?[0-9]+K/ { print $0}'
逻辑表达式示例,显示第3行的内容,NR的含义见下面的内置变量
$ echo -e "a \n b \n c" | awk 'NR == 3 {print $0}'
输出为c。
可以用正则来测试变量是否匹配吗?
当然可以,见BEGIN&END下面的例子。
BEGIN&END
这两个是特殊的模式,跟在BEGIN后面的行为会在awk处理文件之前执行,一些初始化工作(比如设置FS,FS即分隔符)可以放到这里。
而END后面的行为则是在文件被处理完毕之后被执行,善后工作可以放到这里。
比如,下面的脚本统计目录下ctime为2019年的文件或目录数量:
#!/usr/bin/awk -f
# count2019.awk
BEGIN {
CNT = 0; # counter
}
$6~/2019(-[0-9]{2}){2}/ { # 注意这里测试第6个字段是否匹配正则
CNT ++;
}
END {
printf "processing finised, 2019 files/dirs count: %d", CNT
}
运行:
$ ls --full-time | awk -f count2019.awk
processing finised, 2019 files/dirs count: 4⏎
行为
行为在模式的后面,需要用大括号括住。
行为中的语句可以用;
分割,也可以用换行符\n
分隔。
变量
定义变量
awk中的变量直接用就行,和python一样,如果之前没有使用过这个变量,那么它的值为空。
示例见上面的计数器示例
数组
其实awk里面的数组就是python里面的字典,连字符串都能当索引使。
BEGIN {
favorites["singer"] = "taylor swift"
favorites["song"] = "500 miles"
for (key in favorites) {
print favorites[key]
}
}
运行awk -f array.awk
输出结果如下:
500 miles
taylor swift
内置变量
awk中有一些方便的内置变量,如下表(未全部列出):
变量名 | 作用 |
---|---|
FILENAME | 输入的文本文件的文件名,如果是标准输入,空。 |
$0 |
当前记录的内容 |
$1,$2,$3... |
当前记录被分隔符分割成很多字段$num 表示num项(1起始) |
FS | 分隔符,默认空格 |
RS | 输入记录分隔符,默认为\n
|
NF | 当前记录的字段数 |
NR | 已经读入的记录数 |
FNR | 当前输入文件的记录数 |
OFS | 输出字段分隔符,默认空格 |
ORS | 输出记录分隔符,默认换行 \n
|
ARGC | 命令行参数个数 |
ARGV | 命令行参数数组 |
从上面的表中可以看到,其实一个文件是先被RS分隔成记录,然后这个记录再被FS分割成字段,所以准确地说awk其实是以记录为处理单元的,只不过这个RS默认是换行符。
ARGC&ARGV
运行一下下面的程序就知道他们的意思了:
#!/usr/bin/awk -f
# argc.awk
BEGIN{
print ARGC
for (i = 0 ; i < ARGC ; i++) {
print ARGV[i]
}
}
$ awk -f argc.awk /etc/passwd
输出结果:
2
awk
/etc/passwd
函数
函数调用的格式可以是printf("%s %d", "hha", 3)
也可以是printf "%s %d", "hah", 3
示例:
$ ls -l | awk '{printf "%15s %d\n",$9,$5}'
$ ls -l | awk '{printf ("%15s %d\n",$9,$5)}'
内置函数
下面列举一部分常用的内置函数
函数 | 功能 |
---|---|
length(str) |
字符串长度 |
index(str,sub) |
sub在str中的索引(1起始),如果无此子串,0 |
match(str, regex) |
字面意思,0表示不匹配 |
split(str,arr,regex) |
使用regex分割字符,把子字符串放入arr数组 |
printf(format, expr-list) |
这个不解释 |
tolower(str) |
|
toupper(str) |
|
systime() |
自1970-01-01,00:00:00至今的秒数,像不像time调用呢? |
strftime([format [,timestamp[,utc-flag]]]) |
返回一个格式化的时间字符串。示例见下面。 |
close(filename) |
关闭一个文件(awk还能直接输出到文件!) |
delete() |
删除数组中的一个元素 |
exit(code) |
退出,code是返回值 |
getline() |
获取下一行,由于下一行被读了,所以此次记录处理完之后是下下行 |
next() |
读取下一行,然后继续执行 |
system() |
就是stdlib中的system那样的功能 |
systime&strftime
$ awk 'BEGIN {print strftime("%F",systime())}'
自定义函数
看个例子就会了:
#!/usr/bin/awk -f
# strlen.awk
function strlen(str) {
return length(str)
}
BEGIN {
message = "hello"
printf "length of %s is %d", message, strlen(message)
}
控制语句
分支语句
只有一种,那就是if...else...
,和C中一样的用法。
循环语句
四种,分别是while
,do...while
,for
和foreach
(我善做主张起了个名字)
示例如下:
#!/usr/bin/awk -f
# loop.awk
BEGIN {
for (i = 0 ; i < 10 ; i++){
arr[i] = i*2;
}
for (idx in arr) {
printf "arr[%d] = %d\n",idx,arr[idx]
}
i = 9
while (i >= 0) {
arr[i] = 0;
i--;
}
j = 0
do{
print arr[j]
j++
} while(j < 10)
}
运行awk -f loop.awk
结果为:
arr[0] = 0
arr[1] = 2
arr[2] = 4
arr[3] = 6
arr[4] = 8
arr[5] = 10
arr[6] = 12
arr[7] = 14
arr[8] = 16
arr[9] = 18
0
0
0
0
0
0
0
0
0
0
输入输出
可以在awk脚本中直接输出到文件中,这需要用到awk中的重定向功能,和shell里面的重定向一样,使用的符号是>,>>
(当然没有输入重定向)
示例:
#!/usr/bin/awk
# redirect.awk
BEGIN {
print "test" > "/tmp/awkoutput"
close("/tmp/awkoutput") #一定要close,不然不会输出的,而且不关闭会资源泄漏
}
更多高级用法,就在实践中摸索吧!
参考资料:
《Linux就是这个范儿》(好书)
下一篇: gson将json对象转化为java对象