【问题标题】:Bash split stdin by null and pipe to pipelineBash将stdin按null拆分并管道传输到管道
【发布时间】:2018-07-10 03:30:28
【问题描述】:

我有一个以空值分隔的流,其中包含未知数量的部分。对于每个分隔的部分,我想将其通过管道传输到另一个管道,直到最后一个部分被读取,然后终止。 在实践中,每个部分都非常大(~1GB),所以我想在不将每个部分读入内存的情况下这样做。

例如,假设我有由以下人员创建的流:

for I in {3..5}; do seq $I; echo -ne '\0'; 
done

我会得到一个看起来像这样的蒸汽:

1
2
3
^@1
2
3
4
^@1
2
3
4
5
^@

当通过cat -v 传输时。

我想通过paste -sd+ | bc 管道传输每个部分,所以我得到一个看起来像这样的流:

6
10
15

这只是一个例子。实际上流更大,流水线更复杂,所以不依赖流的解决方案是不可行的。

我尝试过类似的方法:

set -eo pipefail
while head -zn1 | head -c-1 | ifne -n false | paste -sd+ | bc; do :; done

但我只得到

6
10

如果我离开 bc 我会得到

1+2+3
1+2+3+4
1+2+3+4+5

这基本上是正确的。这让我相信这个问题可能与缓冲以及每个进程实际上与它们之间的管道交互的方式有关。

有没有办法修复这些命令交换流的方式,以便我可以获得所需的输出?或者,有没有办法通过其他方式来实现?

原则上,这与this question 有关,我当然可以编写一个程序,将标准输入读入缓冲区,查找空字符,并将输出通过管道传输到衍生的子进程,正如接受的答案对那个问题所做的那样.鉴于 bash 中对流和空分隔符的普遍支持,我希望做一些更“原生”的事情。特别是,如果我想走这条路,我将不得不在字符串中转义管道 (paste -sd+ | bc),而不是让同一个 shell 解释它。这本身并没有什么不好的地方,但它有点难看,并且需要一些容易出错的转义。

编辑

正如答案中所指出的,head 不保证它缓冲了多少。除非它一次只缓冲一个字节,这是不切实际的,否则这永远不会奏效。因此,似乎唯一的解决方案是将其读入内存或write a specific program

【问题讨论】:

  • xargs -0 -n 1 在这里有用吗?
  • 你真的需要bc吗?如果你只是使用整数,为什么不坚持使用原生 shell 数学呢?
  • 顺便说一句,回复:set -e,请参阅BashFAQ #105——如果赶时间,请跳过以下练习的寓言。它的行为经常非常不直观(和wildly incompatible between different shells),以至于可以说它弊大于利。
  • @MarkPlotnick 就我对 xargs 手册的阅读而言,它将读取空分隔字符串并将它们传递给命令调用。这不起作用有两个原因,a) 它将它读入内存,b) 如果它填充了当前 shell 的最大命令长度,它将失败。
  • @CharlesDuffy paste -sd+ | bc 只是为了说明一个示例管道。实际情况并非如此。

标签: bash stream pipe delimiter pipeline


【解决方案1】:

您的原始代码的问题是 head 不能保证它的读取量不会超过输出量。因此,它可以消耗超过一个(NUL 分隔的)输入块,即使它只发出一个输出块。

相比之下,read 保证它不会消耗超过您的要求。

set -o pipefail
while IFS= read -r -d '' line; do
  bc <<<"${line//$'\n'/+}"
done < <(build_a_stream)

如果你想要 native 逻辑,没有什么比在 shell 中编写整个东西更原生的了。

调用外部工具(包括bccutpaste 或其他工具)涉及fork() 惩罚。如果您每次调用只处理少量数据,则工具的效率会被启动它们的成本所压倒。

while read -r -d '' -a numbers; do  # read up to the next NUL into an array
  sum=0                             # initialize an accumulator
  for number in "${numbers[@]}"; do # iterate over that array
    (( sum += number ))             # ...using an arithmetic context for our math
  done
  printf '%s\n' "$sum"
done < <(build_a_stream)

对于以上所有内容,我使用以下build_a_stream 实现进行了测试:

build_a_stream() {
  local i j IFS=$'\n'
  local -a numbers
  for ((i=3; i<=5; i++)); do
    numbers=( )
    for ((j=0; j<=i; j++)); do
      numbers+=( "$j" )
    done
    printf '%s\0' "${numbers[*]}"
  done
}

【讨论】:

    【解决方案2】:

    正如所讨论的,唯一真正的解决方案似乎是编写一个专门执行此操作的程序。我写了一个叫xstream-util的生锈。使用cargo install xstream-util 安装后,您可以通过管道输入

    xstream -0 -- bash -c 'paste -sd+ | bc'
    

    得到想要的输出

    6
    10
    15
    

    它并不能避免必须在 bash 中运行程序,因此如果管道复杂,它仍然需要转义。另外,它目前只支持单字节分隔符。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多