我正在使用 netcat 传输数据并将输出传输到 gawk。以下是 gawk 将接收的示例字节序列:
=AAAA;=BBBB;;CCCC==DDDD;
数据几乎包含任意字符,但绝不会包含 NULL 字符,其中=
和;
保留为分隔符。在写入任意字符块时,每个块将始终以其中一个分隔符作为前缀,并始终以其中一个分隔符作为后缀,但可以随时使用任一分隔符:=
并不总是前缀,也不;
总是后缀。它永远不会在没有写入适当的前缀和后缀的情况下写入块。在解析数据时,我需要区分使用了哪个分隔符,以便我的下游代码可以正确解释该信息。
由于这是一个网络流,因此在读取此序列后,stdin 保持打开状态,因为它在等待未来的数据。我希望 gawk 读取直到遇到分隔符,然后使用找到的任何数据执行我的 gawk 脚本的主体,同时确保它正确处理连续的 stdin 流。我将在下面更详细地解释这一点。
迄今
这是我迄今为止尝试过的方法(zsh 脚本,使用 gawk,在 macOS 上)。对于这篇文章,我将主体简化为仅打印数据 - 我的完整 gawk 脚本的主体要复杂得多。我还将 netcat 流简化为cat
一个文件(以及cat
'ing stdin 以模仿流行为)。
cat example.txt - | gawk '
BEGIN {
RS = "=|;";
}
{
if ($0 != "") {
print $0;
fflush();
}
}
'
example.txt
=AAAA;=BBBB;=CCCC;=DDDD;
我的尝试成功处理了大部分数据......直到最新的记录。它挂起等待来自 stdin 的更多数据,并且无法执行最新记录的脚本主体,尽管 stdin 中显然有适当的分隔符。
当前输出:(无法处理最新的记录DDDD
)
AAAA
BBBB
CCCC
[hang here, waiting for future data]
期望输出:(成功处理所有记录,包括最近的记录)
AAAA
BBBB
CCCC
DDDD
[hang here, waiting for future data]
这个问题究竟可能是什么原因造成的?我该如何解决?我意识到这似乎是一种极端情况。非常感谢大家的帮助!
编辑:评论整合、其他澄清以及各种观察/认识
以下是我在调试过程中发现的一些杂项观察,包括我最初发布此帖子之前和之后。这些编辑还澄清了评论中出现的一些问题,并将分散在各个评论中的信息整合到一个地方。还包括我根据评论中极具洞察力的信息对 gawk 内部工作原理的一些认识。此编辑中的信息取代了评论中可能讨论的任何潜在冲突信息。
我简单调查了一下这是否是操作系统强加的管道缓冲问题。在使用该
stdbuf
工具禁用所有管道缓冲后,似乎缓冲根本不是问题,至少不是传统意义上的问题(参见第 3 条)。我注意到,如果 stdin 已关闭并且RS 使用正则表达式,则不会出现问题。相反,如果 stdin 保持打开状态并且RS 不是正则表达式(即纯文本字符串),也不会出现问题。只有当 stdin 保持打开状态并且RS 是正则表达式时才会出现问题。因此,我们可以合理地假设它与正则表达式如何处理连续的 stdin 流有关。
我注意到,如果我的带有正则表达式的 RS (
RS = "=|;";
) 有 3 个字符长...并且 stdin 保持打开状态...它会在 stdin 中出现 3 个额外字符后停止挂起。如果我将正则表达式的长度调整为 5 个字符(RS = "(=|;)"
),则从挂起返回所需的额外字符数量也会相应调整。结合与 Kaz 的极具洞察力的讨论,这确定挂起是正则表达式引擎本身的产物。就像 Kaz 所说的那样,当正则表达式引擎解析时RS = "=|;";
,它最终会尝试从 stdin 读取其他字符以确保正则表达式匹配,尽管这种额外的读取对于所讨论的正则表达式而言并非绝对必要,这显然会导致在等待 stdin 时挂起。我还尝试向正则表达式添加惰性量词,这在理论上意味着正则表达式引擎可以立即返回,但遗憾的是它没有,因为这是正则表达式引擎的实现细节。此处和此处的gawk文档指出,当 RS 是单个字符时,它将被视为纯文本字符串,并导致 RS 匹配而不调用正则表达式引擎。相反,如果 RS 有 2 个或更多字符,它将被视为正则表达式,并将调用正则表达式引擎(随后引发第 3 项中讨论的问题)。但是,这似乎有点误导,这是 gawk 的实现细节。我尝试过
RS = "xy";
(并相应地调整了我的数据),并重新测试了第 3 项中的实验。没有发生挂起,并且打印了正确的输出,这一定意味着尽管 RS 是 2 个字符,但它仍然被视为纯文本字符串 - 正则表达式引擎从未被调用,并且挂起问题从未发生。因此,似乎对 RS 是被视为纯文本还是正则表达式进行了进一步的过滤。那么……既然我们已经找出了问题的根本原因……我们该怎么做呢?一个显而易见的想法是避免使用正则表达式引擎……但我的脚本仍然需要使用某种 OR 子句来匹配分隔符……因此,这似乎需要编写一个自定义数据解析器作为 C 程序或其他程序。虽然我可以这样做,而且它肯定会解决问题,但考虑到手头的任务,我宁愿不走这条杂草丛生的道路。
这让我们想到了 Ed Morton 的解决方法,这很可能是最好的方法,或者可能是其一些衍生方法。下面总结了他的方法:
基本上,在将数据提供给主要 gawk 调用之前,使用其他 CLI 工具或 shell 读取循环,甚至多次调用 gawk,提前进行转换。就像 Ed 说的那样,将每个分隔符替换为全部以 NULL 字符为后缀。由于这是在 gawk 看到任何数据之前完成的,因此可以将 gawk 设置为使用 NULL 字符作为 RS,这将被视为纯文本字符串而不是正则表达式,这意味着正则表达式挂起问题永远不会发生。从那里,真正的分隔符和数据块可以以您想要的任何方式进行解码和处理。
gawk 本身甚至能够进行提前转换……只要可以使用纯文本 RS 而不是正则表达式 RS 找到每个相关分隔符。请小心包含正则表达式专用字符的分隔符,因为这可能会导致 gawk 在您不期望的情况下将其视为正则表达式。
虽然我现在已经将 Ed 的答案标记为解决方案,但我认为我最终的解决方案将是 Ed 的方法、Kaz 的洞察力以及我后来对他们的一些认识的结合。希望我能将两个答案标记为解决方案!谢谢大家的帮助,特别是 Ed Morton 和 Kaz!