答案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
。但请注意,其非常容易一不小心就造成僵局。让我解释一下——以你假设的“循环”为例。您将命令的输出提供给其输入。至少有两种方式可能导致僵局:
该命令有一个输出缓冲区。它已部分填充,但尚未刷新(实际上已写入)。一旦充满就会这样做。所以它会返回读取输入。它会永远坐在那里,因为它等待的输入实际上在输出缓冲区中。在获得输入之前它不会被刷新......
该命令有一堆输出要写入。它开始写入,但内核管道缓冲区已满。所以它坐在那里,等待缓冲区中有空间。一旦它读取输入,就会发生这种情况,即永远不会发生,因为直到它完成向输出写入任何内容之前它不会这样做。
也就是说,这就是你如何做到的。此示例使用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
)- 但我不知道这些东西是如何运作的。我只知道您可以使用上面的语法来完成几乎相同的事情,而无需在bash
and中执行所有繁琐的操作- 并且您可以在andzsh
中执行非常相似的操作,以通过此处文档达到相同的目的dash
busybox ash
(因为dash
和busybox
do 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
都会同时产生一次迭代。