Bash将null用stdin分割,并通过管道传递到管道

时间:2018-07-10 03:30:28

标签: bash stream pipe delimiter pipeline

我有一个用空分隔的流,其中有未知数目的部分。对于每个定界部分,我希望将其通过管道传递到另一个管道中,直到读取完最后一个部分,然后终止。 实际上,每个部分都非常大(〜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有关,我当然可以编写一个程序,将stdin读入缓冲区,查找空字符,然后将输出通过管道传递给生成的子进程,就像接受的答案那样。这个问题。鉴于bash中流和空定界符的普遍支持,我希望做一些“本机”的事情。特别是,如果我想走这条路线,我将不得不使用字符串对管道(paste -sd+ | bc)进行转义,而不仅仅是让相同的shell对其进行解释。内在没有什么不好的,但这有点丑陋,并且需要一些容易出错的转义。

编辑

正如答案中指出的那样,head不能保证缓冲多少。除非它一次仅缓冲单个字节,这是不切实际的,否则它将永远无法工作。因此,似乎唯一的解决方案是将其读入内存或write a specific program

2 个答案:

答案 0 :(得分:1)

原始代码存在的问题是head不保证其读取的内容不会超过输出内容。因此,即使它仅发出一个输出块,它也可以消耗多个(NUL分隔)输入块。

相比之下,

read保证它的消耗不会超出您的要求。

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

如果您想使用本机逻辑,那么,只需要在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
}

答案 1 :(得分:1)

如前所述,唯一真正的解决方案似乎是编写一个程序专门用于此操作。我写了一个名为xstream-util的锈迹。使用cargo install xstream-util安装后,您可以将输入通过管道传输到

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

获得所需的输出

6
10
15

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