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

Abd*_*red 19 command-line shell scripting pipe

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

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

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

在此处输入图片说明

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

pseudo-code:

a = 1    # start condition 

repeat 
{
b = tripple(a)
c = sin(b) 
a = c + 1 
}
Run Code Online (Sandbox Code Playgroud)

Joh*_*024 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...]
Run Code Online (Sandbox Code Playgroud)

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

$ 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...]
Run Code Online (Sandbox Code Playgroud)

在这里,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...]
Run Code Online (Sandbox Code Playgroud)


der*_*ert 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
Run Code Online (Sandbox Code Playgroud)

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


mik*_*erv 5

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

首先,它似乎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
Run Code Online (Sandbox Code Playgroud)

...这会打印...

b=3
c=.14112000805986722210
a=1.14112000805986722210
Run Code Online (Sandbox Code Playgroud)

但当然,它不会持续太久。一旦负责 的printf管道的子 shell 退出(在printf写入a()\n管道之后),管道就会被拆除,并且bc的输入关闭,它也会退出。这远没有想象中那么有用。

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

碰巧,您的 shell 经常这样做。如果您使用实现进程替换的shell ,那么您有一种非常简单的方法来获取持久管道 - 您可以将其分配给可以与之通信的后台进程。

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

bash -cx ': <(:)'
+ : /dev/fd/63
Run Code Online (Sandbox Code Playgroud)

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

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

...打印...

hey cat here
Run Code Online (Sandbox Code Playgroud)

现在我知道不同的 shell 以不同的方式执行协处理- 并且有一种特定的语法用于bash设置一个(也可能有一个zsh - 但我不知道这些东西是如何工作的。我只知道你可以使用上面的语法来做几乎相同的事情,而无需在bashand中使用所有的繁琐内容 - 并且你可以在andzsh中做非常相似的事情,以使用此处文档达到相同的目的(因为and在这里做 -带有管道的文档而不是像其他两个那样的临时文件)dashbusybox ashdashbusybox

所以,当应用到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="$!"
Run Code Online (Sandbox Code Playgroud)

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

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

...打印...

b=3
c=.14112000805986722210
a=1.14112000805986722210
#...24 more lines...
b=3.92307618030433853649
c=-.70433330413228041035
a=.29566669586771958965
Run Code Online (Sandbox Code Playgroud)

...它仍在运行...

echo a >&"$BCIN"
read a <&"$BCOUT"
echo "$a"
Run Code Online (Sandbox Code Playgroud)

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

.29566669586771958965
Run Code Online (Sandbox Code Playgroud)

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

kill "$BCPID"; exec 3>&- 4>&-
unset BCPID BCIN BCOUT
Run Code Online (Sandbox Code Playgroud)

  • 很有意思。请注意,在最近的 bash 和 zsh 中,您不必指定文件描述符,例如 `eval "exec {BCOUT}&lt;&gt;"&lt;(:) "{BCIN}&lt;&gt;"&lt;(:)` 也可以使用 (2认同)