首页 > 解决方案 > 使用 awk 将 }{ 解释为 RS 并使用 ORS }\n{ 输出

问题描述

我的数据如下所示:

{"anonymousId":"abc123",{"hello":"world"}}{"anonymousId":"abc456",{"hi": "again"}}

就好像您获取了一个以换行符分隔的 json 文件并删除了所有换行符。

我正在尝试使用 awk 将其转换为 ndjson。

也就是说,我的预期输出是这样的:

{"anonymousId":"abc123",{"hello":"world"}}
{"anonymousId":"abc456",{"hi": "again"}}

我不想将整个文件加载到内存中(这就是我不使用 sed 的原因),所以我的想法是我应该}{用作行分隔符。然后,我想如果我}\n{用作 ORS,我应该得到我想要的输出。

所以我尝试了这个:

cat my-file.txt | awk -v RS="}{" -v ORS="}\n{" '{$1=$1}1'

但它不起作用!

这是我得到的输出:

{"anonymousId":"abc123",{"hello":"world"}
{}
{{"anonymousId":"abc456",{"hi": "again"}
{}
{}
{

除了不将整个文件加载到内存中的限制之外,我不在乎使用什么 bash 命令,但我的想法是 awk 会是这样。例如,如果tr支持多字符表达式,那对我来说很好。

请帮助我理解为什么这不能按预期工作以及我需要改变什么。

谢谢!

更新

按照给出的答案,将增加一些学习。

如果您需要做这样更棘手的事情,TLDR 是不要使用 macOS。

一方面,这在 mac 上不起作用:echo -e "a\nb\nc\nd\ne\n" | head -n -2; 它抱怨非法行参数,但这在 linux 系统上是有效的。

另一个问题是 awk 在我的 (mac) 系统上的工作方式。

我的 awk 命令接近正确。

在 linux 上,它会产生以下输出:

{"anonymousId":"abc123",{"hello":"world"}}
{"anonymousId":"abc456",{"hi": "again"}}}
{

所以我只需要找到一种方法来修剪尾随}\n{ (正如答案中所指出的那样,{$1=$1}没有必要)

但是所有这些无关的换行符都是由于 awk 在我的系统上的错误实现(它不是 gawk,我不确定它是什么)。

标签: awk

解决方案


$1=$1在里面做awk -v RS='}{' -v ORS='}\n{' '{$1=$1}1' file没有用 - 它告诉 awk 重新编译当前记录,用空格替换所有空格链,但是您示例中唯一的空格是\n文件末尾的空格,并且没有必要将其转换为空格。所以你的脚本可以简化为:

awk -v RS='}{' -v ORS='}\n{' '1' file

RS='}{'对不同的 awk 变体意味着不同的东西。

将多字符 RS 与 GNU awk(现在可能还有其他几个)一起使用意味着 RS 被视为正则表达式来分隔记录:

$ awk -v RS='}{' -v ORS='}\n{' '1' file
{"anonymousId":"abc123",{"hello":"world"}}
{"anonymousId":"abc456",{"hi": "again"}}
}
{$

请注意最后}\n{添加的额外内容,因为输入末尾没有}{,因此输入本身的结尾表示记录的结尾,因此被 ORS 值替换。

将多字符 RS 与 POSIX awk 一起使用意味着 RS 中的第二个和后续字符被忽略,第一个字符被视为 RS,因此您报告在问题中看到的输出:

$ awk --posix -v RS='}{' -v ORS='}\n{' '1' file
{"anonymousId":"abc123",{"hello":"world"}
{}
{{"anonymousId":"abc456",{"hi": "again"}
{}
{
}
{$

其中每个 }人都被视为匹配 RS,因此被 ORS 替换。

所以你没有使用支持多字符 RS 的 awk。您的选择是安装一个(最好是 gawk)并执行以下操作:

$ awk -v RS='}[{\n]' '{ORS=gensub(/}{/,"}\n{",1,RT)} 1' file
{"anonymousId":"abc123",{"hello":"world"}}
{"anonymousId":"abc456",{"hi": "again"}}

否则用任何 awk 做这样的事情:

$ awk --posix -v RS='{' -v ORS= '{print pfx $0; pfx=(/}$/ ? "\n" : "") RS}' file
{"anonymousId":"abc123",{"hello":"world"}}
{"anonymousId":"abc456",{"hi": "again"}}

在上面的 gawk 解决方案中,我们将 RS 定义为'}[{\n]'表示中间行的记录由 终止,}{但行尾的记录由 终止}\n。所以 RT}{对于每条记录都成立,除了行上的最后一条记录,}\n如果你的行以\n或 NULL 结尾,否则我们只需将 ORS 设置为 RT,但对于那些 RT 具有该值的记录}{转换为,否则 ORS 只是如果您的输入没有终止,则}\n{设置为RT 具有该值或 NULL 。}\n\n

我认为我可能更喜欢的另一种 gawk 解决方案是:

$ awk -v RS='}{' -v ORS='}\n{' 'NR>1{print prev} {prev=$0} END{printf "%s",prev}' file
{"anonymousId":"abc123",{"hello":"world"}}
{"anonymousId":"abc456",{"hi": "again"}}

编辑:在我注意到 OP 说他们不想将整个文件读入内存之前,后代的原始答案:

像这样对单个字符串进行简单替换是 sed 最擅长的:

$ sed 's/}{/}\n{/g' file
{"anonymousId":"abc123",{"hello":"world"}}
{"anonymousId":"abc456",{"hi": "again"}}

否则使用任何 awk:

$ awk '{gsub(/}{/,"}\n{")} 1' file
{"anonymousId":"abc123",{"hello":"world"}}
{"anonymousId":"abc456",{"hi": "again"}}

推荐阅读