Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

awk 入坑指北 #1

Open
Jiavan opened this issue Aug 3, 2019 · 0 comments
Open

awk 入坑指北 #1

Jiavan opened this issue Aug 3, 2019 · 0 comments

Comments

@Jiavan
Copy link
Owner

Jiavan commented Aug 3, 2019


title: awk 入坑指北
date: 2018-01-10 21:40:27
tags:

  • awk
    category:
  • linux

awk(/ɔːk/) 是 *nix 下一种强大的文本处理工具,其名称取自三位作者 Alfred Aho,Peter Jay Weinberger,Brian Wilson Kernighan。awk 提供的功能包括不仅限于正则匹配、流控制、算术运算、甚至于函数等编程语言具备的一些特性。它发行于 1977 年,已经超过了 40 岁堪称古董级软件。为了更让人能直接明白它的用途,我把它称为是命令行下的 Excel,其中的一些概念和 Excel 有很多相似之处。

TR;DR

  • awk 是一种高效的文本处理工具。
  • awk 脚本的基本结构 BEGIN -> BODY -> END。
  • 基本语法,awk 脚本语法和 C 语言很很多相似之处。
  • awk 脚本编程实践以及需要注意的地方。

基本用法

在 linux 环境下我们有一些文本处理需求时,awk 就能派上用场了,比如 nginx 的访问日志,文本信息去重等等。awk 可以直接在命令行中执行比如:

echo "" | awk '{print "hello world!"}'

也可以将一些逻辑比较复杂的代码写到一个 awk 脚本中,然后指定脚本文件执行:

awk -f a.awk test.txt

程序结构

awk 直接在命令行中输入和执行 awk 脚本效果是一样的,所以以下代码为了便于阅读都写入到一个脚本文件中。
一个完整的 awk 脚本分为三个部分:

BEGIN{ print("BEGIN");  }
{ print("BODY");  }
END{ print("END");  }

但是 awk 脚本要能正确执行只需要其中一个阶段就行,第一个阶段是 BEGIN,就是还未开始扫描输入文本时候的预处理时间,中间 BODY 是扫描文本执行阶段,awk 每扫描到输入文本一行数据就会执行 BODY 中的脚本代码,最后是 END,即文本扫描完成的阶段,在绝大部分使用场景中我们直接在 {} 中写代码就可以了,即省略掉了 BEGIN 和 END。

awk 理解输入

前面已经提到,我将 awk 视为 *nix 下的 Excel,下面提到的一些概念和 Excel 颇为相似。对于 awk 处理的文本一般情况下是一行一行的,比如 nginx 的日志文件,在 awk 的视角文本的一行被视为一条记录(Record)行以 \n 来进行划分。在一行中通过空格又被分为多个域(Field),这就和 Excel 的行列差不多了。所以在 awk 程序中存在 2 个全局变量 NF,NR,这就很容易理解了。

需要值得注意的是域的索引不是从 0 开始的,而是从 1 开始,0 是当前行的引用,通过 $n 的语法来拿到不同域的值。

{
    # 当前记录有多少个域
    print("NF is:" NF);
    # 当前是第几条记录
    print("NR is:" NR);
    # 输出当前行的文本
    print($0);
    # 输出当前行的第一个域
    print($1);
}

以上代码中 print 是 awk 的一个内置函数,用于输出文本信息。在 awk 脚本中可以通过 # 号来写注释。

awk 语法

和很多脚本程序一样,awk 没有严格的数据类型,不同数据类型直接可以直接转换。在 awk 中声明的变量统统会成为全局变量,所以在一处修改了变量值,脚本其他地方也会受到影响。在流程控制上几乎和主流编程语言完全一样。if-else, while 都是支持的,在后续的列子中可以看到。

传入参数

在命令行下通过 -v 参数还可以给 awk 脚本传入参数,比如:

echo '' | awk -v name="jiavan" '{print("hello, "name);}'

内置函数

awk 内置了很多字符串处理以及数学运算方面的函数,常用的如:

  • index(str1, str2):返回 str2 在 str1 中出现的索引。
  • length(str):返回一个字符串的长度。
  • substr(str, n, m):截取字符串 str 的子串。
  • match(str, reg):正则匹配,返回第一次匹配索引。
    .....
    跟多使用可以 man 一下。

内置变量

awk 还内置了一些变量名:

$0 | 当前处理记录
$1-$n | 当前记录的n个字段
FS | 域分隔符
NF | 当前记录的域数目
NR | 已经处理的记录条数
RS | 记录分隔符
OFS | 输出字段分隔符
ORS | 输出记录分隔符
ARGC | 命令行参数个数
ARGV | 命令行参数数组
FILENAME | 当前输入文件名字
IGNRECASE | 记录为真代表忽略大小写匹配
ARGIND | 当前被处理文件的ARGV标志符
CONVFMT | 数字转换格式 %.6g
ENVIRON | UNIX环境变量
ERRNO | UNIX系统错误消息
FIELDWIDTHS | 输入字段宽度的空白分隔字符串
FNR	| 当前记录数
OFMT | 数字的输出格式 %.6g
RSTART | 被匹配函数匹配的字符串首
RLENGTH | 匹配函数匹配的字符串长度
SUBSEP | \034
PROCINFO | 当前运行程序进程信息数组

awk 实践

文本去重

有以下具有重复文本的文件 name.txt。

// name.txt
jiavan
kellen
lzy
fakafang
jiavan
lzy
pig

// a.awk
{
    ++names[$1];
}
END {
    for (key in names) {
        print(key) > "./unique.txt";
    }
}

上方 a.awk 先遍历文件行存入一个数组,在 awk 中数组的 key 可以是任意数据类型,value 也可以是任意数据类型,名称每出现一次 +1,最后就能统计出每个名称出现的次数以及将所有不重复的 key 输出到一个文件中,这里的操作和 bash 类似。

搜寻字符串

对于输入的文本 words.txt,我们使用 awk 找出带有 av 关键字的单词。

// words.txt
this is a test file with some key words like average and avoid
--created by jiavan.

// b.awk
{
    while (match($0, /[a-zA-Z]+?av[a-zA-Z]+?/)) {
        print(substr($0, RSTART, RLENGTH));
        $0 = substr($0, RSTART + RLENGTH);
    }
}

// result
$ awk -f b.awk words.txt
average
avoid
av
jiavan
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant