Ali*_*Ali 5 shell pipe buffer fifo
的/tmp/in,/tmp/out并且/tmp/err已经创建的命名管道,以及一些进程打开(分别用于阅读,写作和写作)。
我想创建一个新进程,将其 stdin 导入/tmp/in,并将 的内容写入/tmp/out其标准输出,并将 的内容/tmp/err写入其标准错误。一切都应该以行缓冲的方式工作。当创建的另一个进程/tmp/in停止读取并关闭时,该进程应该退出/tmp/in。该解决方案应该适用于 Ubuntu,最好不安装任何额外的软件包。我想在 bash 脚本中解决它。
Mikeserv指出,没有SSCCE,很难理解我想要什么。所以,下面是一个 SSCCE,但请记住,这是一个最小的例子,所以它非常愚蠢。
原来的设置
父进程启动子进程并通过子进程的 stdin 和 stdout 逐行与它通信。如果我运行它,我会得到:
$ python parent.py
Parent writes to child: a
Response from the child: A
Parent writes to child: b
Response from the child: B
Parent writes to child: c
Response from the child: C
Parent writes to child: d
Response from the child: D
Parent writes to child: e
Response from the child: E
Waiting for the child to terminate...
Done!
$
Run Code Online (Sandbox Code Playgroud)
父母.py
from __future__ import print_function
from subprocess import Popen, PIPE
import os
child = Popen('./child.py', stdin=PIPE, stdout=PIPE)
child_stdin = os.fdopen(os.dup(child.stdin.fileno()), 'w')
child_stdout = os.fdopen(os.dup(child.stdout.fileno()))
for letter in 'abcde':
print('Parent writes to child: ', letter)
child_stdin.write(letter+'\n')
child_stdin.flush()
response = child_stdout.readline()
print('Response from the child:', response)
assert response.rstrip() == letter.upper(), 'Wrong response'
child_stdin.write('quit\n')
child_stdin.flush()
print('Waiting for the child to terminate...')
child.wait()
print('Done!')
Run Code Online (Sandbox Code Playgroud)
child.py,必须是可执行的!
#!/usr/bin/env python
from __future__ import print_function
from sys import stdin, stdout
while True:
line = stdin.readline()
if line == 'quit\n':
quit()
stdout.write(line.upper())
stdout.flush()
Run Code Online (Sandbox Code Playgroud)
所需的设置和骇人听闻的解决方案
父源文件和子源文件都不能编辑;不允许。
我将 child.py 重命名为 child_original.py(并使其可执行)。然后,我放置了一个名为 child.py 的 bash 脚本(如果您愿意,可以使用代理或中间人),在运行之前自己启动 child_original.py,python parent.py并让 parent.py 调用现在是我的 bash 脚本的假 child.py,在 parent.py 和 child_original.py 之间转发。
假孩子.py
#!/bin/bash
parent=$$
cat std_out &
(head -n 1 shutdown; kill -9 $parent) &
cat >>std_in
Run Code Online (Sandbox Code Playgroud)
将start_child.sh在执行父前开始child_original.py:
#!/bin/bash
rm -f std_in std_out shutdown
mkfifo std_in std_out shutdown
./child_original.py <std_in >std_out
echo >shutdown
sleep 1s
rm -f std_in std_out shutdown
Run Code Online (Sandbox Code Playgroud)
执行它们的方式:
$ ./start_child.sh &
[1] 7503
$ python parent.py
Parent writes to child: a
Response from the child: A
Parent writes to child: b
Response from the child: B
Parent writes to child: c
Response from the child: C
Parent writes to child: d
Response from the child: D
Parent writes to child: e
Response from the child: E
Waiting for the child to terminate...
Done!
$ echo
[1]+ Done ./start_child.sh
$
Run Code Online (Sandbox Code Playgroud)
这个骇人听闻的解决方案有效。据我所知,它不符合行缓冲要求,并且有一个额外的关闭FIFO通知start_child.sh child_original.py 已关闭管道并且start_child.sh 可以安全退出。
该问题要求改进伪 child.py bash 脚本,满足要求(行缓冲,当 child_original.py 关闭任何管道时退出,不需要额外的关闭管道)。
我希望我知道的事情:
open已经阻塞。这是令人难以置信的反直觉。另请参阅为什么只读打开命名管道块?setDamon(true)这些线程)。否则,JVM 将永远挂起,即使每个人都完成了。尽管与该问题无关,但其他陷阱包括:绕过与 Runtime.exec() 方法相关的陷阱。如果你摆脱了杀戮和关闭的东西(这是不安全的,在极端但并非深不可测的情况下,你可能会在子shell最终结束一些无辜的进程child.py之前死亡 ),那么就不会终止,因为你不是表现得不像一个好的 UNIX 公民。(head -n 1 shutdown; kill -9 $parent) &kill -9child.pyparent.py
子cat std_out &进程将在您发送消息时完成quit,因为写入者std_out是child_original.py,它在接收时完成,quit此时它关闭其stdout,这是std_out管道,这close将使cat进程完成。
尚未cat > std_in完成,因为它正在从源自该进程的管道中读取数据parent.py,并且该parent.py进程没有费心关闭该管道。如果确实如此,cat > stdin_in那么整个过程child.py将自行完成,并且您不需要关闭管道或部分killing(如果由于快速引起的竞争条件,在 UNIX 上杀死不是您子进程的进程始终是一个潜在的安全漏洞)应发生 PID 回收)。
管道右端的进程通常只有在读取完标准输入后才会完成,但由于您没有关闭该 ( child.stdin),因此您隐式地告诉子进程“等等,我有更多输入给您”并且然后你就可以杀死它,因为它确实会等待你的更多输入。
简而言之,让parent.py行为合理:
from __future__ import print_function
from subprocess import Popen, PIPE
import os
child = Popen('./child.py', stdin=PIPE, stdout=PIPE)
for letter in 'abcde':
print('Parent writes to child: ', letter)
child.stdin.write(letter+'\n')
child.stdin.flush()
response = child.stdout.readline()
print('Response from the child:', response)
assert response.rstrip() == letter.upper(), 'Wrong response'
child.stdin.write('quit\n')
child.stdin.flush()
child.stdin.close()
print('Waiting for the child to terminate...')
child.wait()
print('Done!')
Run Code Online (Sandbox Code Playgroud)
你child.py可以像这样简单
#!/bin/sh
cat std_out &
cat > std_in
wait #basically to assert that cat std_out has finished at this point
Run Code Online (Sandbox Code Playgroud)
(请注意,我删除了 fd dup 调用,因为否则您需要关闭两者child.stdin和child_stdin重复项)。
由于parent.pygnu 以面向行的方式运行,因此 gnucat是无缓冲的(正如 mikeserv 指出的那样)并且child_original.py以面向行的方式运行,因此您实际上已经得到了整个行缓冲。
关于 Cat 的注释:无缓冲可能不是最幸运的术语,因为 gnucat确实使用缓冲区。它不会做的是在写出内容之前尝试使整个缓冲区充满(与 stdio 不同)。基本上,它向操作系统发出特定大小(其缓冲区大小)的读取请求,并写入收到的任何内容,而无需等待获取整行或整个缓冲区。(read(2)可能会很懒,只提供它当前可以提供的内容,而不是您要求的整个缓冲区。)
(您可以在http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/cat.c检查源代码;safe_read(使用而不是 plain read)位于gnulib子模块中,它是一个非常简单的包装器围绕read(2)进行抽象EINTR(参见手册页))。