是否有类似xargs的东西而不是使用参数来传递数据?

时间:2019-11-28 18:23:52

标签: shell parallel-processing xargs

xargs非常有用,如果您有一个通过命令行参数接受输入的命令,但是如果您有一个通过stdin接受输入的命令,它就没有那么有用了。

对于这种情况,我看到的建议是使用tee将行复制到几个下游进程,例如:

producer | tee >(consumer0 >out0) >(consumer1 >out1) >(consumer2 >out2) | consumer3 >out3;
cat out* | next-stage-of-pipeline

这具有所有消费者都接受所有生产线的缺点,并且假设consumer{0..3}是不同的过程。如果我需要每个使用者都相同,并处理部分输入(例如,将顺序使用者并行化),那么它就不能正常工作。

我正在寻找的是类似于xargs的东西,它允许在同一使用者的多个实例之间拆分工作,然后组合输入并继续进行处理。输出的组合方式应模仿xargs,因此不能保证行的顺序,但是不会将两行拼接在一起(例如:“ Hello Fred”和“ Hello George”可能以任何顺序出现,但您不会看到“ HHello GeoFregedello”)。

诸如此类的主要用途是处理大量数据,如果对输入的每一行都进行了旋转,则消费者的启动延迟将很明显。如果consumer的启动费用不高,我只需将其包装在一个小的Shell脚本中,该脚本便将其参数传递到consumer中,并使用xargs进行调用。

至少就我所想到的用例而言,生产者将从内部服务中获取大量数据,此后,消费者将需要转换该数据并进行一些API调用。因此,生产者和消费者都将是长时间运行的流程,并且并行运行API调用确实可以加快处理速度。

像这样的东西是理想的,但是我一直找不到能做到这一点的东西:

producer | ??? -P20 consumer | next-stage-of-pipeline

是否有提供此功能的命令行工具?

3 个答案:

答案 0 :(得分:1)

  

在同一使用者的多个实例之间拆分工作

这可以在xargs的消费者端完成。

例如:

consumer() {
   consumer$(($RANDOM % 4)) "$@"
}
export -f consumer
producer | xargs -P20 bash -c consumer --

会选择一个随机的消费者。

该:

consumer() {
   seq 4 |
   xargs -i{} consumer{} "$@"
}
export -f consumer
producer | xargs -P20 bash -c consumer --

将为每个消费者运行输入。

无论如何,你应该明白这个主意。

对于您的发球台,无需任何临时文件即可轻松实现:

consumer() { sed "s/^/$1: /"; }
producer() { seq 3; }
next-stage-of-pipeline() { sed "s/^/Result: /"; }
producer |
{ tee >(consumer 0 >&10) >(consumer 1 >&11) >(consumer 2 >&12) | consumer 3 >&14 ;} 10>&1 11>&1 12>&1 14>&1 | 
next-stage-of-pipeline

如果您需要将每个消费者的输入分成4个,仍然很容易做到:

filter() { awk -vN="$1" '(NR + N) % 4 == 1'
producer |
{ tee >(
     filter 1 | consumer 0 >&10
) >(
     filter 2 | consumer 1 >&11
) >(
     filter 3 | consumer 2 >&12
) | 
     filter 4 | consumer 3 >&14 
;} 10>&1 11>&1 12>&1 14>&1 | 
next-stage-of-pipeline

答案 1 :(得分:1)

我认为GNU Paralell可能会满足您的需求。

producer | parallel consumer | next command in pipeline

编辑:直到开始玩parallel之前,我完全都不了解这个问题。并行可以将一个函数用作使用者,并且您可以在该函数内部使用流。

例如

consumer () 
{ 
    echo "$@" | awk '{print $2}'
}
export -f consumer
for i in {1..30}; do echo "foo $i bar"; done | parallel consumer

编辑2:

您可以使用--pipe选项,该选项将数据传输到使用者。 -q参数将shell引号放在使用者周围:

for i in {1..30}; do echo "foo $i bar"; done | parallel --pipe -q awk '{print $2}'

奖金:

  1. parallel将在顺序处理器上运行作业。如果您有8个核心和16个使用方,则每个核心将获得2个使用方(可配置)
  2. parallel可以在网络上的多台计算机上运行,​​只要它可以ssh对它们运行,而不会提示输入密码(例如,使用ssh-agent)。

答案 2 :(得分:0)

GNU Parallel可以将标准输入分割为同一命令:

... | parallel --pipe consumer

默认情况下,它在\n上斩波,块大小约为1 MB。可以使用--recstart / --recend--block进行更改。

如果输入是文件,则速度更快:

parallel -a bigfile --pipepart --block -1 consumer

这将找到bigfile的大小,并将其分成每个CPU线程一个块。这是即时完成的-因此不会创建临时文件。

https://zenodo.org/record/1146014的第9章对此进行了详细介绍。

但是,如果您真的希望将整个输入传递给不同的使用者,GNU Parallel也可以这样做:

# Pipe 1..10 to consumer-a..z
seq 10 | parallel --pipe --tee 'echo consumer-{}; cat' ::: {a..z}