leafee98-blog/content/essays/treat-quotes-and-brackets-as-a-whole-in-awk.md
leafee98 8b696cada9
All checks were successful
ci/woodpecker/push/deploy Pipeline was successful
up essays: treat-quotes-and-brackets-as-a-whole-in-awk
2023-07-09 17:14:11 +08:00

2.6 KiB
Raw Blame History

title date tags categories weight show_comments draft description
在 Awk 中将引号或括号中的内容视作一个整体 2023-07-09T16:14:35+08:00
50 true false gawk 会将 FPAT 所匹配到的内容作为字段field的一部分因此可以方便地处理需要将引号或括号中的内容作为一个整体的情况。

通常 awk 中可以使用 -F 或者 FS 变量来指定各个字段field的分隔符这个变量支持正则因此能够满足相当大部分的需求但是一个字段中包含分隔符时则极难处理而 gawk 中的 FPAT 变量所提供的功能则提供了一个十分方便的处理方式。

⚠️注意 FPAT 是 gawk 所提供的扩展选项,如果你使用其他 awk 实现请注意检查是否支持此特性

与 FS 将所匹配到的内容作为非字段内容不同FPAT 会将匹配到的内容作为字段内容。

https://www.gnu.org/software/gawk/manual/html_node/Splitting-By-Content.html

以下面的 Apache http 的一行日志为例,如果将 FS 设置为空格,那么 HTTP 请求中的空格会使之被分割成多个字段 “Get/index.xmlHTTP/1.1"

1.2.3.4 - - [09/Jul/2023:00:11:51 +0000] "GET /index.xml HTTP/1.1" 200 96917 "-" "Mozilla/5.0 (compatible; Miniflux/2.0.45; +https://miniflux.app)"

硬要写能够满足这个例子的 FS 分隔符至少需要用到正则表达式的 lookaround 特性,但是 gawk 并不支持正则的这一特性。

如果使用 FPAT 的特性,可以写出下面三种正则,分别匹配方括号里面的时间、双引号中间的 HTTP 请求和 User-Agent、中间没有空格的其他字段。

\[.*\]
"[^"]+"
[^ ]+

将上面三个正则用括号包括再使用正则中的“或”逻辑,就得到了下面第一行的正则,但是由于这些正则要写在双引号中做字符串,所以需要对双引号和斜杠进行转义,得到下面第二行字符串。

(\[.*\])|("[^"]+")|([^ ]+)
"(\\[.*\\])|(\"[^\"]+\")|([^ ]+)"

所以最后的效果像下面这样

$ cat content
1.2.3.4 - - [09/Jul/2023:00:11:51 +0000] "GET /index.xml HTTP/1.1" 200 96917 "-" "Mozilla/5.0 (compatible; Miniflux/2.0.45; +https://miniflux.app)"

$ cat test.awk
BEGIN {
    FPAT="(\\[.*\\])|(\"[^\"]+\")|([^ ]+)"
}

{
    for (i = 1; i <= NF; i++) {
        printf("%d: <%s>\n", i, $i)
    }
}

$ awk -f test.awk content
1: <1.2.3.4>
2: <->
3: <->
4: <[09/Jul/2023:00:11:51 +0000]>
5: <"GET /index.xml HTTP/1.1">
6: <200>
7: <96917>
8: <"-">
9: <"Mozilla/5.0 (compatible; Miniflux/2.0.45; +https://miniflux.app)">