【问题标题】:How to make pipe run sequentially如何使管道按顺序运行
【发布时间】:2015-08-21 08:17:16
【问题描述】:

规格

不认为这会有所作为,但无论如何我都会包含它

GNU bash,版本 3.2.51


前提

假设我有一个包含多个部分的管道,如何防止部分管道在前一部分完成之前运行。


示例

在下面的示例中,我将尝试显示问题

$ echo hello | tee /dev/tty | (echo "Next";sed 's/h/m/' )

输出

Next
hello
mello

用 sleep 表示是定时关闭

$ echo hello | tee /dev/tty | (sleep 2;echo "Next";sed 's/h/m/' )

输出

hello
Next
mello

预期输出

如上

hello
Next
mello

但这显然取决于睡眠时间比上一个命令完成所需的时间长,这不是我想要的。


注意事项

我知道有更好的方法可以做到这一点,但我认为准确了解管道的工作原理对我来说是有教育意义的。


试过

尝试了等待和睡眠的变体,但没有任何效果始终如一。


l0b0's suggestion

这仍然会先打印 Next

$ echo hello | tee /dev/tty | sort |(echo "Next";sed 's/h/m/' )
Next
hello
mello

$ echo hello | tee /dev/tty | tac | tac |(echo "Next";sed 's/h/m/' )
Next
hello
mello

如果需要更多信息,请告诉我。

【问题讨论】:

  • 使用 () 在 next 和 sed 中创建另一个管道,这通常是为什么您最终会按照您的想法打印 Next ,但顺序不正确。如果你用大括号表达式{} 替换它,那么tac | tac 操作(如果有的话,你可以单独使用sponge)应该可以正常工作。
  • @Petesh as you consider, out of order,不必居高临下,这显然是不正常的。此外,如果我使用大括号,那么它会挂起/等待输入。另外,您所说的无论如何都没有意义,因为即使它在another pipeline(子shell 不是管道,没有管道)中,sed 仍然会执行。
  • 听起来并不居高临下;这不是我的意图。 {} 的语法更严格一些 - 您必须在大括号的左右两边留有空格,并在命令的后面加上 ;,例如{ echo "Next"; sed 's/h/m'; }。我刚刚注意到它在我尝试过的另一个系统上不起作用。但是,如果您尝试对 echo 和 sed 进行排序,则需要一个尾随 | tac | tac
  • @Petesh 是的,我省略了尾随的;,谢谢。但是在现在尝试之后,它仍然首先打印NEXT。也肯定只使用cat 而不是tac | tac 将具有与等待输入相同的效果,还是不会?
  • 目的是在可能异步运行的东西周围强制序列点,所以你最终得到的是:echo hello | tee /dev/tty | cat | ( echo "Next"; sed 's/h/m/' ) | cat

标签: linux bash unix pipe


【解决方案1】:

管道的重点是异步处理数据,从而整体节省时间和空间。如果您想拥有一个同步管道,您不妨写入文件(如果您需要速度,则在 RAM 磁盘上)。但是对于接收命令能够以块的形式处理数据的任务,完整的管道可能要慢得多:

  1. a | b | c 最多只能与三个命令中最慢的命令一样快。
  2. a > file; b < file > file2; c < file2 最多可以与每个命令的运行时间的总和一样快。

因此,如果所有命令都在大约 N 秒内运行(单独运行时),那么您将看到第一个命令的最佳运行时间为 N,第二个命令的运行时间为 3N。

【讨论】:

  • 是的,我知道它们是异步运行的,但想知道是否有办法在管道的后半部分中告诉命令,第一部分中的命令在开始之前已经完成。正如我在问题中提到的,我也已经知道有更好的方法(使用文件)。当命令从前一个管道接收数据时,它们如何知道等待。例如,第二个 sed 将始终在数据通过 tee 后执行?
  • 你的新建议不起作用,我会更新我的问题
【解决方案2】:

bash 中没有语言结构可以根据需要修改管道的行为。但是,您可以使用命名管道来充当一种二进制信号量:

mkfifo block
echo hello | 
  { tee /dev/tty; echo go > block; }  |
  (read < block; echo "Next"; sed 's/h/m/' )

read 命令会阻塞,直到有内容写入命名管道,直到 tee 完成后才会发生。

(请注意,这可能无法完全解决您的问题,因为除了进程同步之外,您可能需要处理多个进程写入同一个输出文件的事实,并且您无法完全控制各种写入的多路复用方式(由于缓冲等)

【讨论】:

  • 如果回显的文本大于管道的缓冲区大小,则此代码将阻塞,因为 tee 无法将其输出推送到完整的管道中。
【解决方案3】:

反向两次技巧按预期工作:出于显而易见的原因,tac(1) 需要在将结果写入输出之前消耗整个输入,因此使用tac | tac 确保管道中的下一个命令不会在之前开始读取输入上一个命令完成。注意我说的是不开始读取输入,而不是不开始执行。正如您将看到的,这非常重要。

这里的问题是您正在调用一个子shell,其中第一个命令不依赖于可用的输入。 echo(1) 不会阻塞等待输入,因此这本质上是一种竞争条件:子shell 进程与tee(1) 命令竞争,以查看谁先写入终端。管道提供基于输入可用性的同步,如果管道中的某些进程在不依赖输入可用性的情况下工作,则该进程必然会与管道中的其他进程竞争;你无法阻止它。

要修复它,您需要以某种方式打印Next,仅当shell 在管道中有可用输入时。一个快速的技巧是使用另一个 sed(1) 命令来完成,该命令将每一行的开头替换为 Next\n

echo hello | tee /dev/tty | tac | tac | ( sed -e 's/^/Next\n/' | sed 's/h/m/' )

这可行,但语义并不完全相同:现在,字符串Next\nsed 's/h/m/' 输入的一部分。在此示例中这不是问题,因为 Next\n 没有出现字母 h,但请考虑到此 hack 更改了输入流 - 对于您的特定用例,这可能是也可能不是问题。

【讨论】:

  • 您好,感谢您的回答。您的解决方案更多的是针对此特定示例的解决方案,而不是对整个问题的解决方案。我真的想以某种方式阻止非阻塞命令,直到它们接收到来自前一个管道的输入。其实你只是给了我一个想法,谢谢!我会在一分钟内报告。
  • @User112638726 我真的想以某种方式阻止非阻塞命令,直到它们接收到来自前一个管道的输入。除非命令从管道读取,否则通常不能这样做.因此,您将始终需要一些技巧/解决方法来使用输入,以便您可以利用管道的同步功能。你能举出这个答案不起作用的具体例子吗?
  • 我没有具体的例子,我只是想知道是否有通用的解决方案。我只是想知道这是否可能,但从这里的回复来看,它不是(没有破解)。不过我很感谢你的回答。谢谢:)
【解决方案4】:

您可以通过使用cat 并将结果存储在一个变量中来强制完成读取流:

$ echo hello | tee /dev/tty | ( echo before; x="$(cat)"; echo after; sed s/h/m <<<"$x" )
before
hello
after
mello

【讨论】:

  • 这是我的最佳答案(不需要tac),谢谢!
【解决方案5】:

这似乎可以工作,并且不需要命名管道。

$ echo hello | tee >(tac|(echo "Next";sed 's/h/m/')) | cat
hello
Next
mello

我避免使用 /dev/tty,因为它并不总是可用(如 SSH),并且尾随的 'cat' 会导致 'tee' 等待子 shell 完成。

【讨论】:

    猜你喜欢
    • 2023-03-09
    • 2022-09-26
    • 1970-01-01
    • 1970-01-01
    • 2020-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-29
    相关资源
    最近更新 更多