【问题标题】:Split and recombine stdin in Bash script在 Bash 脚本中拆分和重组标准输入
【发布时间】:2018-03-22 10:42:39
【问题描述】:

假设我有流式文本数据进入我的 Bash 脚本,每行一条记录,我想在每一行附加该行的一些函数并将其吐到 stdout

record1      record1  fn(record1)
record2  ->  record2  fn(record2)
...          ...

这相对容易做到,比如 Awk。但是,假设我应用于输入数据的函数在应用于流数据时效率要高出几个数量级(而且我有很多,因此绝对不能选择逐行 Awk 处理)。这是我想出的解决方案:

input="$(mktemp)"
trap "rm -rf ${input}" EXIT
cat > "${input}"
paste "${input}" <(some_function "${input}")

这通过将stdin 重定向到一个临时文件(cat 行),然后使用进程替换将文件paste 合并在一起来实现。但是,这对我来说似乎有点混乱(例如,UUOC),我认为可能有一种“更好”的方式来使用exec(用于重定向)和tee,但我不太确定如何.

这可以做得更好吗?

【问题讨论】:

  • fn(recordX) 是用recordX 调用fn 的结果
  • 假设即使您可以一次性从标准输入读取多行,但您希望按记录写入输出(每行唯一),对吗?在这种情况下,肯定需要一次处理一行?你不觉得
  • 是的,从这个意义上说,但我的观点是,如果应用于整个流而不是逐行调用,我上面的some_function 会快得多。为了更具体:some_function 这里是从输入中提取一个字段,然后对其进行 base64 解码,然后根据某些标准对其进行分类。如果逐行运行,base64 解码步骤是瓶颈;有点技巧,它可以在流数据上运行以获得相同的结果,并且它可以在几分钟而不是几小时/几天内运行......我上面的解决方案非常有效,我只是想知道是否有更好/更好的方法。
  • 是的,完全同意您在读取方面的观点,但关键是您的写入逻辑在每一行中都是唯一的,对吧?您不能在每个唯一行上进行批量写入吗?有意义吗?
  • 是的,这是有道理的。我的解决方案计算 fn(record1)fn(recordN) 的值,然后 paste 将它们重新组合在一起。 POSIX 流/缓冲的性质使它们保持同步,这实际上是“批量写入”,但我看不出这一点与我的问题的相关性。

标签: bash redirect stream pipe tee


【解决方案1】:

简单 (?) 解决方法

最简单的解决方案可能是修改您的函数,使其输入始终在其实际输出之前打印。数学上:将f(x)=y 替换为g(x)="x f(x)"

如果您无法轻松修改函数,请使用以下方法之一。

无文件方法

如果你有足够的内存,你可以通过将标准输入保存到一个变量来修改你当前的无文件工作方法:

input="$(cat)"
paste - <(yourFunction <<< "$input") <<< "$input"

实际答案

编辑: 这可能不适用于大文件。 tee 可以交错两个输出。我正在寻找更好的解决方案。我不知道我是否会找到一个更好的。如果您发现使此方法安全的修改,请随时编辑此答案。

(tee >(yourFunction) | pr -Ts --columns 2)

第一部分打印标准输入,然后是标准输入通过您的函数管道传输。如果输入作为文件file 给出,则与cat file &lt;(yourFunction &lt; file) 基本相同。

第二部分为paste 1st_half_of_output 2nd_half_of_output

示例

假设以下三行作为标准输入给出

Lorem Ipsum
What a nice 2nd line
\t\r\n[]().*?+<>\

你的功能是

f() { awk '{print "#" NR}'; }

然后tee &gt;(f) 将打印(编辑: 订单无法保证。请参阅上面的注释。

Lorem Ipsum
What a nice 2nd line
\t\r\n[]().*?+<>\
#1
#2
#3

pr 会将输出转换为

Lorem Ipsum #1
What a nice 2nd line    #2
\t\r\n[]().*?+<>\   #3

在每个# 之前都有一个制表符作为列分隔符。

【讨论】:

    【解决方案2】:

    这并没有消除对临时文件的需要,但它至少摆脱了cat的无用使用:

    input="$(mktemp)"
    trap "rm -rf ${input}" EXIT
    tee "${input}" | paste - <(some_function "${input}")
    

    【讨论】:

    • n.b.,虽然这可行,但它不适用于大型数据集。当它达到管道缓冲区大小时,它似乎失败了,所以cat 它是。
    猜你喜欢
    • 1970-01-01
    • 2012-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多