Abd*_*red 19 command-line shell scripting pipe
我知道两种命令如何相互连接:
我不知道这是否可行,所以我画了一个假设的连接类型:
怎么可能在命令之间实现循环数据流,例如在这个伪代码中,我使用变量而不是命令。:
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
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. 但是请注意,很容易意外地造成死锁。让我解释一下——以你假设的“圆形”为例。您将命令的输出提供给其输入。至少有两种方式可能会陷入僵局:
该命令有一个输出缓冲区。它已部分填充,但尚未刷新(实际上已写入)。一旦填满它就会这样做。所以它回到读取它的输入。它会永远留在那里,因为它等待的输入实际上在输出缓冲区中。它不会被刷新,直到它得到那个输入......
该命令有一堆输出要写入。它开始写入,但内核管道缓冲区已满。所以它坐在那里,等待他们在缓冲区中留出空间。这将在它读取其输入后立即发生,即永远不会因为它在完成向其输出写入任何内容之前不会这样做。
也就是说,这就是你如何做到的。这个例子是用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那里的调用,以禁用缓冲。没有它?没有任何输出的死锁。
您知道,我不相信您一定需要如图所示的重复反馈循环,尽管您可以在协进程之间使用持久管道。话又说回来,可能并没有太大的区别——一旦你在协进程上打开一行,你就可以实现典型的样式循环,只需向其中写入信息并从中读取信息,而不需要做任何非常不寻常的事情。
首先,它似乎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)