我知道两种类型的命令如何相互连接:
- 通过使用管道(将std-output放入下一个命令的std-input中)。
- 通过使用T恤(将输出拼接成许多输出)。
我不知道这是否可能,所以我画了一个假设的连接类型:
如何在命令之间实现循环数据流,例如在此伪代码中,我使用变量而不是命令。
pseudo-code:
a = 1 # start condition
repeat
{
b = tripple(a)
c = sin(b)
a = c + 1
}
我知道两种类型的命令如何相互连接:
我不知道这是否可能,所以我画了一个假设的连接类型:
如何在命令之间实现循环数据流,例如在此伪代码中,我使用变量而不是命令。
pseudo-code:
a = 1 # start condition
repeat
{
b = tripple(a)
c = sin(b)
a = c + 1
}
Answers:
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...]
您可以为此使用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
那里的呼叫,以禁用缓冲。没有它?没有任何输出的死锁。
通常,我将使用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
,但不必要:如果您使用中间文件,为什么不仅仅使用循环来管理它呢?
make
与宏有关,在这里是完美的应用程序。
您知道,我不认为您在图表描绘时一定需要一个重复的反馈循环,以至于您可能在协过程之间使用了持久的管道。再说一遍,可能没有太大的区别-在协同处理中打开一行后,您可以实现典型的样式循环,只需向其中写入信息并从中读取信息,而无需做任何不寻常的事情。
首先,它似乎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
-您可以在此处进行非常相似的操作dash
并busybox ash
达到相同的目的(因为dash
并busybox
在此处执行-带管道的文档,而不是其他两个文档的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"
......这只是让我的最后价值bc
的a
,而不是调用a()
函数来增加它并打印...
.29566669586771958965
实际上,它将继续运行,直到我杀死它并拆除其IPC管道为止。
kill "$BCPID"; exec 3>&- 4>&-
unset BCPID BCIN BCOUT
eval "exec {BCOUT}<>"<(:) "{BCIN}<>"<(:)
工程,以及