通常 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.xml
、 HTTP/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)">