LWN:AWK近况如何?

关注了就能看到更多这么棒的文章哦~

The state of the AWK

May 19, 2020

This article was contributed by Ben Hoyt

原文来自:https://lwn.net/Articles/820829/

主译:DeepL

AWK是一种文本处理语言,已经有40多年的历史了。它有一个专门的POSIX标准定义,还有几个符合标准的实现,并且在2020年仍然是个非常有意义的工具,很令人惊讶。无论是简单的文本处理任务,还是处理 "大数据",都是如此。最近发布的GNU Awk 5.1给了我们一个很好的理由来调查总结一下AWK如今的情况,看看GNU Awk都做了些什么,再看看AWK最近都用在什么地方。

AWK语言是1977年在Bell实验室创建的。它的名字来自于原作者的首字母缩写。Alfred Aho, Peter Weinberger 和 Brian Kernighan。作为一个Unix工具,AWK的设计核心目的是为了做好一件事:逐行过滤和转换文本。它通常被用来解析日志文件中的字段、对其他工具的输出内容进行转换、以及计算单词和字段的出现次数。Aho对AWK的功能进行了简明扼要的总结:

AWK一次读一行输入信息。对每一行都会扫描程序中的每一个pattern,对于每一个匹配上的pattern,都会执行相关的操作。

AWK program往往是直接在命令行中执行的单行操作。例如,假如我们有一些web服务器日志,要从中计算出GET请求的平均响应时间,你可以键入

$ awk '/GET/ { total += 6; n++ } END { print total/n }' server.log

0.0186667

这句话的意思是:对所有能匹配正则表达式/GET/的行,都将响应时间(第六个字段即$6)相加,并计算该行的响应时间;最后打印出响应时间的算术平均值。

The various AWK versions

目前,AWK有三个主要版本,它们都符合POSIX标准(至少对绝大多数场景来说基本符合POSIX)。第一个是经典的AWK,也就是Aho、Weinberger和Kernighan在他们的《The AWK Programming Language》一书中描述的AWK。它有时被称为 "new AWK"(nawk)或 "one true AWK",现在它被托管在GitHub上。这是在很多基于BSD的系统上预装的版本,包括macOS(虽然macOS自带的版本已经过时,应该升级了)。

第二种是 GNU Awk (gawk),这是迄今为止功能最丰富、维护最积极的版本。Gawk通常在Linux系统中预装,通常是默认的awk。它很容易在macOS上使用Homebrew安装,也有Windows的二进制版本。Arnold Robbins从1994年起就一直是gawk的主要维护者,并继续对gawk语言进行维护(他还为经典的awk版本贡献了许多fix)。Gawk有许多awk或POSIX标准中没有的功能,包括一些新函数、网络功能、C extension API、profiler和debugger,以及最近新增的namespace功能。

第三种常见的版本是mawk,由Michael Brennan编写。它是Ubuntu和Debian Linux上的默认awk,仍然是AWK的最快版本,它有bytecode compiler和并且每个数值占用的内存更小。(Gawk从4.0开始也有了bytecode compiler,所以现在它的速度更接近mawk的速度了)。

如果你想用awk来做单行命令操作或者基本的文本处理,以上任何一个都可以用。如果你想用它来做一个规模很大的脚本程序,Gawk的特点更加适合这一需求。

AWK还有其他几个成熟度和维护程度各不相同的AWK实现,特别是在嵌入式Linux环境中使用的size非常小的BusyBox版本,一个具有Java语言功能的Java重写版本,以及我自己的GoAWK,一个用Go编写的符合POSIX的版本。这三个主要的AWK和BusyBox版本都是用C语言编写的。

Gawk changes since 4.0

自从上次LWN报道gawk 4.0发布以来,已经过去了将近10年。如果我们宣称 "自2011年以来,Gawk发生了很多变化",这听起来很有吸引力。但事实上,在AWK世界里,发展相对缓慢。我将在这里描述自4.0以来的显著功能,更多细节你可以阅读完整的4.x和5.x的changelog。Gawk 5.1.0版本在4月14日刚发布,刚刚过去一个多月。

在5.0中,用户可能关心的最大的功能改动就是引入了namespace(命名空间)。大多数现代语言都有一些命名空间的概念,这样可以更方便地发布大型项目和库,不用担心命名冲突。Gawk 5.0以向后兼容的方式加入了命名空间,允许开发者创建库,比如这个toy math库。

    # area.awk
    @namespace "area"

    BEGIN {
        pi = 3.14159  # namespaced "constant"
    }

    function circle(radius) {
        return pi*radius*radius
    }

要引用库中的变量或函数,请使用命名空间:::name语法,类似于C++。

    $ gawk -f area.awk -e 'BEGIN { print area::pi, area::circle(10) }'
    3.14159 314.159

Robbins认为,AWK缺乏命名空间是AWK作为一种大规模的编程语言还没有被广泛使用的关键原因之一,而gawk 5.0中的这个功能会有助于解决这个问题。Robbins认为阻碍AWK发展的另一个主要问题是缺乏一个好的C语言扩展接口。Gawk的动态扩展接口(dynamic extension interface)在4.1中进行了彻底的改造;现在它有一个定义非常美好的API,并允许对现有的C和C++库进行封装,这样就可以很容易地从AWK中调用。

下面的代码片段来自用户手册中的C代码包装器示例,它用一个文件名和stat()系统调用的值来填充一个AWK数组(一个字符串键的哈希表)。

    /* empty out the array */
    clear_array(array);

    /* fill in the array */
    array_set(array, "name", make_const_string(name, strlen(name), &tmp));
    array_set_numeric(array, "dev", sbuf->st_dev);
    array_set_numeric(array, "ino", sbuf->st_ino);
    array_set_numeric(array, "mode", sbuf->st_mode);

在4.2版本中的另一个变化(并在5.0版本中延续了下来)是对源代码中的pretty-printer进行了全面的修改。Gawk的pretty-printer是作为一个标准化的AWK code formatter来用的,类似于Go的go fmt工具和Python的Black code format工具。例如,要想把上面的area.awk 文件很漂亮地输出出来,可以这样做:

    $ gawk --pretty-print -f area.awk

输出如下:

    @namespace "area"

    BEGIN {
        pi = 3.14159    # namespaced "constant"
    }


    function circle(radius)
    {
        return (pi * radius * radius)
    }

你可能会质疑这个工具的一些做法:为什么 "BEGIN {"在表示函数的"{"符号前面没有换行(其实是因为AWK语法不允许这样做)?为什么在函数前有两行空行,而在返回表达式周围有一对括号?但至少这样的显示内容非常一致,能有助于避免代码风格的争论。

Gawk允许功能很有限的runtime type inspection运行时类型检查,并在4.2中增加了typeof()函数,进一步改进了这方面的支持。对于需要递归遍历某个嵌套数组中的每一个项目的代码时,这个功能就很重要了(这是POSIX AWK无法做到的)。

在4.2版本中,gawk也支持拿正则表达式常量(regular expression constants)作为first-class data type用在syntax @/foo/中。以前我们不能将正则表达式常量存储在变量中。typeof(@/foo/)会返回字符串 "regexp"。在性能方面,gawk 4.2在Linux系统上带来了显著的改进,当fwrite_unlocked()可用时,可以使用fwrite_unlocked()。由于 gawk 是单线程的,所以它可以使用non-locking stdio函数,从而使原始数据的输出速度提高了 7-18%,例如 gawk ''一个大文件的输出速度就是这种例子。

GNU Awk User's Guide一直都是一个全面的参考文档,但在 4.1 版和 5.x 版中进行了大幅度的更新,包括新的例子、总结和练习,以及一些重要的编辑改动。

最后(也是最不重要的),4.0中的一个微妙的变化是sub()和gsub()中的反斜线的处理方式,我觉得很有趣。Robbins写道:

sub()和gsub()中的反斜线的默认处理方式已经恢复到3.1中的行为。我当初以为我可以为了符合标准而破坏对旧版本的兼容性,我这个想法太天真了。

sub和gsub函数是正则表达式替换函数的核心功能,即使是当初他做得为了遵循标准而对反斜杠的复杂处理逻辑进行了一个小小的 "fix",也破坏了人们现有的代码:

当4.0.0版本发布时,gawk维护者将POSIX规则作为默认规则,打破了十多年来的向后兼容性。不用说,这是个坏主意,从4.0.1版本开始,gawk恢复了它的历史行为,只在给出--posix时才遵循POSIX规则。

Robbins可能在最初的修改中出现了一点小小的判断错误失误,但很明显他对向后兼容的问题很重视。特别是对于像gawk这样的流行工具,有时继续破坏规范比导致现在能用的东西出错是个更优的选择。

Is AWK still relevant?

问AWK是否还有用,就好像是在问氧气对人们是否还有用。你可能看不到它,但它就在你身边。许多 Linux 管理员和 DevOps 工程师用它来转换数据或通过log文件诊断问题。几乎所有基于Unix的机器上都安装了AWK的某个版本。除了临时使用之外,许多大型开源项目也会把AWK用在其build或写文档的工具中。仅举几个例子:Linux内核在x86工具集中使用它来检查和重新格式化objdump文件,Neovim使用它来生成文档,FFmpeg使用它来做build and test。

出人意料的是,即使人们愿意去除AWK build script,事实上也很难做到。2018 年 LWN 写过一篇关于 GCC 贡献者希望用 Python 替换 AWK 来生成option-parsing代码。当时这个提议得到了一些人的支持,但显然没有人自愿进行实际移植,这个AWK 脚本现在还好好地活着。

Robbins在2018年的论文中为AWK(特别是gawk)作为一种 "systems programming language(系统编程语言)"进行了论证,在这里,AWK指的是一种用于编写大型工具和程序的语言。他列举了他认为AWK错过的一些扩张机会,但Kernighan "并不100%相信"这个说法(AWK没有被广泛应用于大型程序的主要原因是缺乏扩展机制)。他提出,其实可能是由于缺乏build-in的机制来访问系统调用。但尽管有这些问题,还是有一些人用它来开发了较大型的工具。Robbins自己的TexiWeb Jr.是个1300行的AWK脚本,实现了literate programming tool;Werner Stoop的d.awk工具,可以从源代码中的Markdown注释中生成文档(800行的awk代码);还有Translate Shell,这是一个6000行的AWK工具,它为基于cloud的翻译API提供了相当强大的命令行接口。

在过去几年中,有几个开发者写过如何在他们的“大数据”工具包中应用AWK的文章,认为AWK是一个比Spark和Hadoop等重型分布式计算系统更简单(有时也更快)的工具。Nick Strayer曾写过一个工具,使用AWK和R,利用了多个CPU来解析25 terabyte(万亿字节)的数据。其他大数据的例子还有Adam Drake的文章《命令行工具可以比你的Hadoop集群快235倍》(https://adamdrake.com/command-line-tools-can-be-235x-faster-than-your-hadoop-cluster.html),以及Brendan O'Connor的《不要瞧不起AWK——最快、最优雅的大数据处理语言!》(https://brenocon.com/blog/2009/09/dont-mawk-awk-the-fastest-and-most-elegant-big-data-munging-language/ ,这篇文章的标题很有诱惑力。

有了这些文本处理、编译工具、"系统编程 ",还有大数据处理之外(我就不提基于文本模式的第一人称射击游戏了 https://github.com/TheMozg/awk-raycaster ),看来AWK在2020年的时候,AWK是有生命力的。

感谢Arnold Robbins审阅了这篇文章的初稿。

全文完

LWN文章遵循CC BY-SA 4.0许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注LWN深度文章以及开源社区的各种新近言论~

LWN:AWK近况如何?_第1张图片

你可能感兴趣的:(LWN:AWK近况如何?)