Phi*_*hil 5 zsh pipe shell-script fifo coprocesses
我需要在 zsh shell 脚本中创建一个函数,由命令替换调用,将状态与对相同命令替换的后续调用进行通信。
类似于 C 在函数中的静态变量(非常粗略地说)。
为此,我尝试了两种方法 - 一种使用协处理器,一种使用命名管道。命名管道方法,我无法开始工作 - 这令人沮丧,因为我认为它会解决我在协处理器上遇到的唯一问题 - 也就是说,如果我从终端进入一个新的 zsh shell,我似乎没有能够看到父 zsh 会话的 coproc。
我已经创建了简化的脚本来说明下面的问题 - 如果您对我正在尝试做的事情感到好奇 - 它正在向子弹列车 zsh 主题添加一个新的有状态组件,该组件将由命令替换 build_prompt( ) 函数在这里:https : //github.com/caiogondim/bullet-train.zsh/blob/d60f62c34b3d9253292eb8be81fb46fa65d8f048/bullet-train.zsh-theme#L692
脚本 1 - 协处理器
#!/usr/bin/env zsh
coproc cat
disown
print 'Hello World!' >&p
call_me_from_cmd_subst() {
read get_contents <&p
print "Retrieved: $get_contents"
print 'Hello Response!' >&p
print 'Response Sent!'
}
# Run this first
call_me_from_cmd_subst
# Then comment out the above call
# And run this instead
#print "$(call_me_from_cmd_subst)"
# Hello Response!
read finally <&p
echo $finally
Run Code Online (Sandbox Code Playgroud)
脚本 2 - 命名管道
#!/usr/bin/env zsh
rm -rf /tmp/foo.bar
mkfifo /tmp/foo.bar
print 'Hello World!' > /tmp/foo.bar &
call_me_from_cmd_subst() {
get_contents=$(cat /tmp/foo.bar)
print "Retrieved: $get_contents"
print 'Hello Response!' > /tmp/foo.bar &!
print 'Response Sent!'
}
# Run this first
call_me_from_cmd_subst
# Then comment out the above call
# And run this instead
#print "$(call_me_from_cmd_subst)"
# Hello Response!
cat /tmp/foo.bar
Run Code Online (Sandbox Code Playgroud)
在它们的初始形式中,它们都产生完全相同的输出:
$ ./named-pipe.zsh
Retrieved: Hello World!
Response Sent!
Hello Response!
$ ./coproc.zsh
Retrieved: Hello World!
Response Sent!
Hello Response!
Run Code Online (Sandbox Code Playgroud)
现在,如果我将 coproc 脚本切换为使用命令替换进行调用,则没有任何变化:
$ ./named-pipe.zsh
Retrieved: Hello World!
Response Sent!
Hello Response!
$ ./coproc.zsh
Retrieved: Hello World!
Response Sent!
Hello Response!
Run Code Online (Sandbox Code Playgroud)
也就是说,从命令替换创建的子进程读取和写入协进程不会导致问题。我对此感到有点惊讶 - 但这是个好消息!
但是,如果我在命名的管道示例中进行相同的更改,脚本会阻塞 - 没有输出。为了尝试判断我为什么用 运行它zsh -x
,给出:
+named-pipe.zsh:3> rm -rf /tmp/foo.bar
+named-pipe.zsh:4> mkfifo /tmp/foo.bar
+named-pipe.zsh:15> call_me_from_cmd_subst
+call_me_from_cmd_subst:1> get_contents=+call_me_from_cmd_subst:1> cat /tmp/foo.bar
+named-pipe.zsh:5> print 'Hello World!'
+call_me_from_cmd_subst:1> get_contents='Hello World!'
+call_me_from_cmd_subst:2> print 'Retrieved: Hello World!'
+call_me_from_cmd_subst:4> print 'Response Sent!'
Run Code Online (Sandbox Code Playgroud)
它看起来对我来说,由命令替换创建将不会终止,而下面的线还没有终止子(我打得使用&
,&!
和disown
这里的结果没有变化)。
# Run this first
#call_me_from_cmd_subst
# Then comment out the above call
# And run this instead
print "$(call_me_from_cmd_subst)"
Run Code Online (Sandbox Code Playgroud)
为了证明这一点,我可以手动触发一只猫来读取响应:
$ cat /tmp/foo.bar
Hello Response!
Run Code Online (Sandbox Code Playgroud)
脚本现在等待最后的 cat 命令,因为管道中没有任何内容可供读取。
我的问题是:
zsh
)到控制台,我就不能再访问它(事实上我可以创建一个新的协程,它将运行独立于其父级并退出,并继续使用父级的!)。解释我在 2 和 3 中的意思:
$ coproc cat
[1] 24516
$ print -p test
$ read -ep
test
$ print -p test_parent
$ zsh
$ print -p test_child
print: -p: no coprocess
$ coproc cat
[1] 28424
$ disown
$ print -p test_child
$ read -ep
test_child
$ exit
$ read -ep
test_parent
Run Code Online (Sandbox Code Playgroud)
我无法从子 zsh 内部看到协进程,但我可以从命令替换子进程内部看到它?
最后我使用的是 Ubuntu 18.04:
$ zsh --version
zsh 5.4.2 (x86_64-ubuntu-linux-gnu)
Run Code Online (Sandbox Code Playgroud)
基于管道的脚本不起作用的原因不是 zsh 的某些特殊性。这是由于 shell 命令替换、shell 重定向和管道的工作方式。这是没有多余部分的脚本。
mkfifo /tmp/foo.bar
echo 'Hello World!' > /tmp/foo.bar &
call_me_from_cmd_subst() {
echo 'Hello Response!' > /tmp/foo.bar &
echo 'Response Sent!'
}
echo "$(call_me_from_cmd_subst)"
cat /tmp/foo.bar
Run Code Online (Sandbox Code Playgroud)
命令替换$(call_me_from_cmd_subst)
创建一个匿名管道,将运行该函数的子 shell 的输出连接到原始 shell 进程。原始进程从该管道读取。子进程创建要运行的孙子进程echo 'Hello Response!' > /tmp/foo.bar
。两个进程都以相同的打开文件开始,包括匿名管道。孙子执行重定向> /tmp/foo.bar
。这会阻塞,因为没有从命名管道读取任何内容/tmp/foo.bar
。
重定向是一个两步过程(实际上是三步,但第三步在这里无关紧要),因为当你打开一个文件时,你不能选择它的文件描述符。该>
运营商希望重定向标准输出,也就是说,它要到特定的文件连接到文件描述符1。这需要三个系统调用:
fd = open("/tmp/foo.bar", O_RDWR)
打开文件。该文件将fd
在进程当前未使用的某个文件描述符上打开。这是阻塞直到某些东西开始从命名管道读取的步骤/tmp/foo.bar
:如果没有人在听,则打开命名管道阻塞。dup2(fd, 1)
除了内核选择的文件描述符之外,还调用以打开所需文件描述符上的文件。如果在新的描述符 (1) 上有任何打开的东西(用于命令替换的匿名管道),则此时它已关闭。close(fd)
,将重定向目标仅保留在所需的文件描述符上。同时,子进程打印Reponse Sent!
并终止。原来的 shell 进程仍在从管道中读取。由于管道仍然打开用于在孙子中写入,因此原始 shell 进程一直在等待。
要解决此僵局,请确保孙子不会让管道保持打开的时间过长。例如:
call_me_from_cmd_subst() {
{ exec >&-; /bin/echo 'Hello Response!' > /tmp/foo.bar; } &
echo 'Response Sent!'
}
Run Code Online (Sandbox Code Playgroud)
或者
call_me_from_cmd_subst() {
{ echo 'Hello Response!' > /tmp/foo.bar; } >/dev/null &
echo 'Response Sent!'
}
Run Code Online (Sandbox Code Playgroud)
或此主题的任意数量的变体。
协进程没有这个问题,因为它不涉及命名管道,所以死锁的一半没有被阻塞:>/tmp/foo.bar
当它打开命名管道时阻塞,但>&p
不会阻塞,因为它只是重定向一个已经打开的文件描述符。
归档时间: |
|
查看次数: |
220 次 |
最近记录: |