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


19

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

  1. 通过使用管道(将std-output放入下一个命令的std-input中)。
  2. 通过使用T恤(将输出拼接成许多输出)。

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

在此处输入图片说明

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

pseudo-code:

a = 1    # start condition 

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

Answers:


16

圆形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...]

12

您可以为此使用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那里的呼叫,以禁用缓冲。没有它?没有任何输出的死锁。


谢谢,在这种情况下,我对缓冲区一无所知,您是否知道一些关键字以了解更多信息?
Abdul Al Hazred

1
@AbdulAlHazred对于输入/输出的缓冲,请查找stdio缓冲。对于管道中的内核缓冲区,管道缓冲区似乎可以正常工作。
derobert

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将产生一个迭代。


可爱滥用make,但不必要:如果您使用中间文件,为什么不仅仅使用循环来管理它呢?
亚历克西斯2015年

@ alexis,makefiles可能会过大。我对循环不太满意:我错过了时钟,暂停条件或清晰示例的概念。最初的图使我想起了工作流程和功能签名。对于复杂的图,我们最终将需要数据连接或makefile类型的规则。(这只是一种直觉)
JJoao 2015年

@alexis,当然,我也同意你的观点。
JJoao 2015年

我不认为这是滥用- make有关,在这里是完美的应用程序。
mikeserv

1
@mikeserv,是的。众所周知,滥用工具是Unix的地下《大宪章》 :)
JJoao 2015年

4

您知道,我不认为您在图表描绘时一定需要一个重复的反馈循环,以至于您可能在协过程之间使用了持久的管道。再说一遍,可能没有太大的区别-在协同处理中打开一行后,您可以实现典型的样式循环,只需向其中写入信息并从中读取信息,而无需做任何不寻常的事情。

首先,它似乎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管道的子外壳一退出(在printf写入a()\n管道后即刻),管道便被拆除,bc的输入也关闭,它也退出了。这几乎没有它可能有用的。

@derobert已经提到了FIFO,这可以通过使用实用程序创建命名管道文件来实现mkfifo。这些也基本上只是管道,只是系统内核将文件系统条目链接到两端。这些非常有用,但是如果您只需要一个管道而又不冒其在文件系统中被监听的风险,那就更好了。

碰巧的是,您的shell经常执行此操作。如果使用实现过程替换的外壳,那么您将拥有一种非常持久的方法来获取持久管道-您可以将其分配给可以与之通信的后台进程。

bash例如,在中,您可以看到流程替换的工作方式:

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

您看到它确实是替代品。壳膨胀期间替换一个值,该值对应于路径的链路到一个。您可以利用它-不必只使用该管道与()替换本身内运行的任何进程进行通信...

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

...打印...

hey cat here

现在,我知道不同的shell 以不同的方式执行协同处理操作 -并且有一种特定的语法bash来设置一种(可能zsh还有一种) -但我不知道这些操作如何工作。我只知道,您可以使用上述语法在不使用rigmarole的情况下执行几乎相同的操作,bash并且zsh-您可以在此处进行非常相似的操作dashbusybox ash达到相同的目的(因为dashbusybox在此处执行-带管道的文档,而不是其他两个文档的temp-file)

所以,当应用于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"

......这只是让我的最后价值bca,而不是调用a()函数来增加它并打印...

.29566669586771958965

实际上,它将继续运行,直到我杀死它并拆除其IPC管道为止。

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

1
很有意思。请注意近期bash和zsh中你没有指定的文件描述符,如eval "exec {BCOUT}<>"<(:) "{BCIN}<>"<(:)工程,以及
雷神
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.