欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Aha awk!

程序员文章站 2022-07-12 15:18:27
...

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中一样的用法。

循环语句

四种,分别是whiledo...whileforforeach(我善做主张起了个名字)

示例如下:

#!/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,不然不会输出的,而且不关闭会资源泄漏
}

更多高级用法,就在实践中摸索吧!

参考资料:

AWK - Built-in Functions

《Linux就是这个范儿》(好书)

相关标签: awk