关于一般性
我认为需要提到的是,这类问题最通用的解决方案涉及两个pass:
- 第一次将十进制行号 ($REC) 添加到每行的前面,有效地将行按 $REC 分组为记录
- 第二遍触发 $REC 的每个新值的第一个实例作为记录边界(重置 $CURREC),然后在有关匹配 $CURREC 的记录的本机 AWK 习语中滚动。
在中间文件中,一些十进制数字序列后跟一个分隔符(出于人为原因,通常是一个添加的制表符或空格)被解析(也就是概念上的剪断)作为相对于基线文件的带外。
命令行粘贴怪物
即使仅限于命令行,确保中间文件永远不会撞到磁盘也是一件容易的事。您只需要使用支持进程替换的高级 shell,例如 ZSH(我自己最喜欢的):
paste <( <input.txt awk "BEGIN { R=0; N=0; } /Header pattern/ { N=1; } { R=R+N; N=0; print R; }" ) input.txt | awk -f yourscript.awk
让我们把那条线更适合展示:
P="/Header pattern/"
X="BEGIN { R=0; N=0; } $P { N=1; } { R=R+N; N=0; print R; }"
paste <( <input.txt awk $X ) input.txt | awk -f yourscript.awk
这将启动三个进程:简单的内联 AWK 脚本paste,以及您最初真正想要运行的 AWK 脚本。
在幕后,<() 命令行构造创建了一个命名管道,并将管道名称作为其第一个输入文件的名称进行粘贴。对于paste 的第二个输入文件,我们将其命名为原始输入文件的名称(因此,此文件由两个不同的进程按顺序并行读取,它们之间最多会消耗一个 从磁盘读取,如果输入文件是冷的)。
中间的神奇命名管道是一个内存中 FIFO,古代 Unix 可能以大约 16 kB 的平均大小管理它(如果yourscript.awk 进程在排空这个 FIFO 时速度缓慢,则会间歇性地暂停 paste 进程)。
也许现代 Unix 在那里抛出了一个更大的缓冲区,因为它可以,但它肯定不是你应该关心的稀缺资源,直到你编写你的第一个 真正 高级命令行,其中包含涉及这些的进程重定向成百上千:-)
其他性能注意事项
在现代 CPU 上,所有这三个进程都可以很容易地发现自己在不同的内核上运行。
这些过程中的前两个实际上是微不足道的:一个带有单个模式匹配和一些小记账的 AWK 脚本,使用两个参数调用粘贴。 yourscript.awk 将很难跑得比这些更快。
什么,您的开发机器没有轻负载的内核,无法在执行域中几乎免费地呈现这种主要的 shell-master 解决方案模式?
响,响。
喂?
嘿,这是给你的。 2018 年刚刚打来电话,希望解决问题。
2020 年正式成为 MTV 的缓刑期:这就是我们喜欢的方式,魔法管道免费,核心免费。更不用说最近在这个领域摇摆不定的任何特定 TLA 芯片供应商了。
作为最终的性能考虑,如果您不想要解析实际记录号的开销:
X="BEGIN { N=0; } $P { N=1; } { print N; N=0; }"
现在,您的 in-FIFO 中间文件仅在每行前附加两个字符('0' 或 '1' 以及由paste 添加的默认分隔符)进行注释,其中 '1' 标记第一行记录。
命名的 FIFO
在底层,这些与 Unix 在您编写任何普通管道命令时实例化的魔法 FIFO 没有什么不同:
cat file | proc1 | proc2 | proc2
三个未命名的管道(以及一个专门用于 cat 的整个过程,您甚至不需要)。
不幸的是,由 shell 预先管理的默认 stdin/stdout 流的真正异常便利性掩盖了paste $magictemppipe1 $magictemppipe2 没有额外的性能考虑值得考虑的现实,在 99%所有情况。
“使用<() Y 型接头,卢克。”
您对问题域中自然语义分解的本能反应将因此受益匪浅。
如果有人一开始就将外壳构造 <() 命名为 YODA 运算符,我怀疑它至少在十年前就会被压入通用服务中。