【问题标题】:How can I split and re-join STDOUT from multiple processes?如何从多个进程拆分和重新加入 STDOUT?
【发布时间】:2014-06-08 23:13:17
【问题描述】:

我正在开发一个管道,它有几个分支点随后合并——它们看起来像这样:

         command2
        /        \
command1          command4
        \        /
         command3

每个命令都写入 STDOUT 并通过 STDIN 接受输入。来自command1 的STDOUT 需要传递给command2 和command3,它们顺序 运行,并且它们的输出需要有效连接并传递给command4。我最初认为这样的事情会起作用:

$ command1 | (command2; command3) | command4

但这不起作用,因为只有来自 command2 的 STDOUT 被传递给命令 4,当我删除 command4 时,很明显 command3 没有从 command1 传递适当的流——换句话说,就好像 command2正在耗尽或消耗流。我得到与 { command2 相同的结果;命令3; } 也在中间。所以我想我应该使用'tee' with process substitution,并尝试了这个:

$ command1 | tee >(command2) | command3 | command4

但令人惊讶的是,这也不起作用 - 似乎 command1 的输出 command2 的输出通过管道传输到 command3,这会导致错误,并且只有 command3 的输出通过管道传输到命令 4.我确实发现以下内容在 command2 和 command3 之间获得了适当的输入和输出:

$ command1 | tee >(command2) >(command3) | command4

但是,这也会将 command1 的输出流式传输到 command4,这会导致出现问题,因为 command2 和 command3 生成的规范与 command1 不同。我得到的解决方案似乎很老套,但确实有效:

$ command1 | tee >(command2) >(command3) > /dev/null | command4

这会抑制 command1 将其输出传递给 command4,同时从 command2 和 command3 收集 STDOUT。它有效,但我觉得我错过了一个更明显的解决方案。我是吗?我已经阅读了几十个线程,但还没有找到适用于我的用例的这个问题的解决方案,也没有看到拆分和重新连接流的确切问题的详细说明(尽管我不能成为第一个一个来处理这个)。我应该只使用命名管道吗?我尝试过,但也很难让它发挥作用,所以也许这是另一个线程的另一个故事。我在 RHEL5.8 中使用 bash。

【问题讨论】:

  • 看起来您的问题有一个解决方案可行 -- 那么您是否要求不同的解决方案?通常,这种拆分不会经常出现在 shell 脚本中,但会经常出现在 Hadoop-MapReduce 等专用工具中——我认为您不会找到比 bash 管道更好的东西。
  • @Soren -- 是的,我想知道是否有更好的解决方案。我并没有为此失眠,因为我的解决方案似乎有效,但我希望有一个解决方案不涉及将 stdout 重定向到 /dev/null 并且我很好奇我犯错的地方,因为它可能会提供信息为我(或其他人)继续发展。

标签: bash stdout stdin pipe tee


【解决方案1】:

你可以像这样玩弄文件描述符;

((date | tee >( wc >&3) | wc) 3>&1) | wc

((command1 | tee >( command2 >&3) | command3) 3>&1) | command4

解释一下,就是tee >( wc >&3)会在stdout上输出原始数据,而内层wc会在FD 3上输出结果。外层3>&1)然后会将FD3的输出合并回STDOUT,所以从两个 wc 都被发送到 tailing 命令。

但是,此管道(或您自己的解决方案中的管道)中没有任何内容可以保证输出不会被破坏。也就是说,command2 中的不完整行不会与 command3 的行混淆——如果这是一个问题,您将需要做两件事之一;

  1. 编写您自己的 tee 程序,该程序在内部使用 popen 并读回每一行,然后将完整的行发送到标准输出以供 command4 读取
  2. 将 command2 和 command3 的输出写入文件并使用 cat 将数据合并为 command4 的输入

【讨论】:

  • 谢谢——这正是我要找的。我也很欣赏关于输出流的潜在插入的说明。我想知道是否有比重写tee 或使用文件更优雅的解决方案。也许我可以使用wait 来保持 command3 直到 command2 完成?
  • 这个解决方案似乎适用于 bash/ksh/zsh。有人知道如何使它与 /bin/static-sh (即busybox)一起工作吗?
【解决方案2】:

另请参阅https://unix.stackexchange.com/questions/28503/how-can-i-send-stdout-to-multiple-commands。在所有答案中,我发现this answer 特别适合我的需要。

扩展一点@Soren 的回答,

$ ((date | tee >( wc >&3) | wc) 3>&1) | cat -n
     1         1       6      29
     2         1       6      29

你可以不使用 tee 而是使用环境变量,

$ (z=$(date); (echo "$z"| wc ); (echo "$z"| wc) ) | cat -n
     1         1       6      29
     2         1       6      29

在我的例子中,我应用了这种技术并编写了一个在busybox下运行的非常复杂的脚本。

【讨论】:

    【解决方案3】:

    我相信您的解决方案很好,它使用了 tee 作为记录。 如果您阅读 tee 的手册页,它会说:

    Copy standard input to each FILE, and also to standard output
    

    您的文件是进程替换。

    标准输出是你需要删除的,因为你不想要它,这就是你将它重定向到 /dev/null 所做的

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-03-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-08
      • 2023-03-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多