如何在互连命令之间实现数据循环流?

如何在互连命令之间实现数据循环流?

我知道命令如何相互连接有两种类型:

  1. 通过使用管道(将标准输出放入下一个命令的标准输入)。
  2. 通过使用 Tee(将输出拼接成多个输出)。

我不知道这是否是可能的,所以我画了一个假设的连接类型:

在此输入图像描述

如何在命令之间实现数据循环流,例如在这个伪代码中,我使用变量而不是命令:

pseudo-code:

a = 1    # start condition 

repeat 
{
b = tripple(a)
c = sin(b) 
a = c + 1 
}

答案1

循环 I/O 循环的实现tail -f

这实现了一个循环 I/O 循环:

$ echo 1 >file
$ tail -f file | while read n; do echo $((n+1)); sleep 1; done | tee -a file
2
3
4
5
6
7
[..snip...]

这使用您提到的正弦算法实现循环输入/输出循环:

$ echo 1 >file
$ tail -f file | while read n; do echo "1+s(3*$n)" | bc -l; sleep 1; done | tee -a file
1.14112000805986722210
.72194624281527439351
1.82812473159858353270
.28347272185896349481
1.75155632167982146959
[..snip...]

这里,bc进行浮点运算,s(...)是 bc 的正弦函数表示法。

使用变量来实现相同的算法

对于这个特定的数学示例,不需要循环 I/O 方法。人们可以简单地更新一个变量:

$ n=1; while true; do n=$(echo "1+s(3*$n)" | bc -l); echo $n; sleep 1; done
1.14112000805986722210
.72194624281527439351
1.82812473159858353270
.28347272185896349481
[..snip...]

答案2

为此,您可以使用由 . 创建的 FIFO mkfifo。但请注意,其非常容易一不小心就造成僵局。让我解释一下——以你假设的“循环”为例。您将命令的输出提供给其输入。至少有两种方式可能导致僵局:

  1. 该命令有一个输出缓冲区。它已部分填充,但尚未刷新(实际上已写入)。一旦充满就会这样做。所以它会返回读取输入。它会永远坐在那里,因为它等待的输入实际上在输出缓冲区中。在获得输入之前它不会被刷新......

  2. 该命令有一堆输出要写入。它开始写入,但内核管道缓冲区已满。所以它坐在那里,等待缓冲区中有空间。一旦它读取输入,就会发生这种情况,即永远不会发生,因为直到它完成向输出写入任何内容之前它不会这样做。

也就是说,这就是你如何做到的。此示例使用od, 创建一个永无止境的十六进制转储链:

mkfifo fifo
( echo "we need enough to make it actually write a line out"; cat fifo ) \ 
    | stdbuf -i0 -o0 -- od -t x1 | tee fifo

请注意,最终会停止。为什么?它陷入僵局,上面#2。您可能还会注意到stdbuf其中的调用,以禁用缓冲。没有它?死锁,没有任何输出。

答案3

你知道,我不相信你一定需要一个重复的反馈循环,正如你的图表所描绘的那样,也许你可以使用执着的之间的管道协进程。话又说回来,可能并没有太大的区别——一旦你在协进程上打开一行,你就可以实现典型的样式循环,只需向其中写入信息和从中读取信息,而不需要做任何非常不寻常的事情。

首先,它似乎bc是您协进程的主要候选者。bc您可以使用define几乎可以完成您在伪代码中要求的功能的函数。例如,执行此操作的一些非常简单的函数可能如下所示:

printf '%s()\n' b c a |
3<&0 <&- bc -l <<\IN <&3
a=1; b=0; c=0;
define a(){ "a="; return (a = c+1); }
define b(){ "b="; return (b = 3*a); }
define c(){ "c="; return (c = s(b)); }
IN

...这会打印...

b=3
c=.14112000805986722210
a=1.14112000805986722210

但当然,它不最后的。一旦负责 的printf管道的子 shell 退出printf(写入a()\n管道后立即)管道被拆除,bc输入关闭,它也退出。这远没有想象中那么有用。

@derobert 已经提到过先进先出可以通过创建一个命名管道使用该mkfifo实用程序创建文件。这些本质上也只是管道,只不过系统内核将文件系统条目链接到两端。这些非常有用,但是如果您可以只拥有一个管道而不冒在文件系统中被窥探的风险,那就更好了。

碰巧,您的 shell 经常这样做。如果您使用的 shell 实现了流程替代那么你有一个非常简单的方法来获得持久管道 - 您可以分配给可以与之通信的后台进程的类型。

例如,在 中bash,您可以看到进程替换是如何工作的:

bash -cx ': <(:)'
+ : /dev/fd/63

你看这确实是一个代换。 shell 在扩展过程中替换一个与路径相对应的值关联到一个管道。您可以利用这一点 - 您不必局限于使用该管道仅与替换()本身中运行的任何进程进行通信......

bash -c '
    eval "exec 3<>"<(:) "4<>"<(:)
    cat  <&4 >&3  &
    echo hey cat >&4
    read hiback  <&3
    echo "$hiback" here'

...打印...

hey cat here

现在我知道不同的外壳可以做协进程以不同的方式进行操作 - 并且有一种特定的语法bash用于设置(也可能是一个zsh- 但我不知道这些东西是如何运作的。我只知道您可以使用上面的语法来完成几乎相同的事情,而无需在bashand中执行所有繁琐的操作- 并且您可以在andzsh中执行非常相似的操作,以通过此处文档达到相同的目的dashbusybox ash(因为dashbusyboxdo here-documents 使用管道而不是临时文件,就像其他两个一样)

所以,当应用到bc...

eval "exec 3<>"<(:) "4<>"<(:)
bc -l <<\INIT <&4 >&3 &
a=1; b=0; c=0;
define a(){ "a="; return (a = c+1); }
define b(){ "b="; return (b = 3*a); }
define c(){ "c="; return (c = s(b)); }
INIT
export BCOUT=3 BCIN=4 BCPID="$!"

...这是最困难的部分。这是有趣的部分......

set --
until [ "$#" -eq 10 ]
do    printf '%s()\n' b c a >&"$BCIN"
      set "$@" "$(head -n 3 <&"$BCOUT")"
done; printf %s\\n "$@"

...打印...

b=3
c=.14112000805986722210
a=1.14112000805986722210
#...24 more lines...
b=3.92307618030433853649
c=-.70433330413228041035
a=.29566669586771958965

...它仍在运行...

echo a >&"$BCIN"
read a <&"$BCOUT"
echo "$a"

bc...这只是让我得到s的最后一个值,a而不是调用a()函数来递增它并打印...

.29566669586771958965

事实上,它会继续运行,直到我杀死它并拆掉它的 IPC 管道......

kill "$BCPID"; exec 3>&- 4>&-
unset BCPID BCIN BCOUT

答案4

一般来说,我会使用 Makefile(命令 make)并尝试将图表映射到 makefile 规则。

f1 f2 : f0
      command < f0 > f1 2>f2

为了具有重复/循环命令,我们需要定义迭代策略。和:

SHELL=/bin/bash

a.out : accumulator
    cat accumulator <(date) > a.out
    cp a.out accumulator

accumulator:
    touch accumulator     #initial value

每个make都会同时产生一次迭代。

相关内容