/tmp/in
、/tmp/out
和是命名管道/tmp/err
,已由某个进程创建并打开(分别用于读取、写入和写入)。
我想创建一个新进程,将其 stdin 通过管道传输到/tmp/in
,并将 的内容写入其 stdout ,并在可用时将/tmp/out
其内容写入其 stderr 。/tmp/err
一切都应该在一个行缓冲时尚。当创建的另一个进程/tmp/in
停止读取并关闭时,该进程应该退出/tmp/in
。该解决方案应该在 Ubuntu 上运行,最好不安装任何额外的软件包。我想在 bash 脚本中解决它。
麦克塞夫指出,如果没有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!
$
父级.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!')
孩子.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()
所需的设置和黑客解决方案
父级源文件和子级源文件均不可编辑;不允许。
我将 child.py 重命名为 child_original.py (并使其可执行)。然后,我放置一个名为 child.py 的 bash 脚本(如果您愿意,可以是代理或中间人),在运行之前自己启动 child_original.pypython parent.py
并让 Parent.py 调用假的 child.py,它现在是我的 bash 脚本,在parent.py和child_original.py之间转发。
假孩子.py
#!/bin/bash
parent=$$
cat std_out &
(head -n 1 shutdown; kill -9 $parent) &
cat >>std_in
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
执行它们的方式:
$ ./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
$
这个黑客解决方案有效。据我所知,它不满足行缓冲要求,并且有一个额外的关闭fifo来通知start_child.sh child_original.py已关闭管道并且start_child.sh可以安全退出。
该问题要求改进假child.py bash脚本,满足要求(行缓冲,当child_original.py关闭任何管道时退出,不需要额外的关闭管道)。
我希望我知道的事情:
- 如果使用高级 API 将 fifo 作为文件打开,则必须将其打开阅读和写作,否则对的调用
open
已经阻塞。这是非常违反直觉的。也可以看看为什么命名管道的只读打开会阻塞? - 实际上,我的父进程是一个 Java 应用程序。如果您使用 Java 的外部进程,请从以下位置读取外部进程的 stdout 和 stderr守护进程线程(调用
setDamon(true)
这些线程前启动它们)。否则,即使每个人都完成了,JVM 也会永远挂起。尽管与问题无关,但其他陷阱包括:绕过与 Runtime.exec() 方法相关的陷阱。 - 显然,无缓冲意味着有缓冲,但我们不会等到缓冲区满了,而是尽快刷新它。
答案1
如果你摆脱了杀戮和关闭的东西(这是不安全的,在极端但并非深不可测的情况下,你可能会在子shell最终结束一些无辜的进程child.py
之前死亡 ),那么就不会终止,因为你不是表现得不像一个好的 UNIX 公民。(head -n 1 shutdown; kill -9 $parent) &
kill -9
child.py
parent.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!')
你child.py
可以像这样简单
#!/bin/sh
cat std_out &
cat > std_in
wait #basically to assert that cat std_out has finished at this point
(请注意,我删除了 fd dup 调用,因为否则您需要关闭两者child.stdin
和child_stdin
重复项)。
由于parent.py
gnu 以面向行的方式运行,因此 gnucat
是无缓冲的(正如 mikeserv 指出的那样)并且child_original.py
以面向行的方式运行,因此您实际上已经得到了整个行缓冲。
关于猫的注意事项:无缓冲可能不是最幸运的术语,因为 gnucat
确实使用缓冲区。它不会做的是在写出内容之前尝试使整个缓冲区充满(与 stdio 不同)。基本上,它向操作系统发出特定大小(其缓冲区大小)的读取请求,并写入收到的任何内容,而无需等待获取整行或整个缓冲区。 (阅读(2)可能会很懒,只给你当前可以给你的东西,而不是你所要求的整个缓冲区。)
(您可以在以下位置检查源代码http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/cat.c; safe_read
(使用而不是 plain read
)位于gnulib
子模块中,它是一个非常简单的包装器阅读(2)抽象掉EINTR
(参见手册页))。
答案2
使用 a 时,sed
输入将始终在行缓冲区中读入,并且可以使用该w
命令显式地刷新每行输出。例如:
( cd /tmp; c=
mkfifo i o
dd bs=1 <o&
sed -n w\ o <i&
while sleep 1
do [ -z "$c" ] && rm [io]
[ "$c" = 5 ] && exit
date "+%S:%t$((c+=1))"
done| tee i
)
44: 1
44: 1
45: 2
45: 2
46: 3
46: 3
47: 4
47: 4
48: 5
48: 5
30+0 records in
30+0 records out
30 bytes (30 B) copied, 6.15077 s, 0.0 kB/s
...在哪里tee
(这是指定不阻塞缓冲区)同时将其输出写入终端和sed
管道i
。逐行读取并sed
立即将读取的每一行写入其ut 管道。一次读取ut 管道一个字节,并且它与 共享一个标准输出,因此它们同时将输出写入终端。如果没有明确的行缓冲,这种情况就不会发生。这是相同的运行,但没有rite 命令:i
w
o
dd
o
tee
sed
w
( cd /tmp; c=
mkfifo i o
dd bs=1 <o&
sed '' >o <i&
while sleep 1
do [ -z "$c" ] && rm [io]
[ "$c" = 5 ] && exit
date "+%S:%t$((c+=1))"
done| tee i
)
48: 1
49: 2
50: 3
51: 4
52: 5
48: 1
49: 2
50: 3
51: 4
52: 5
30+0 records in
30+0 records out
30 bytes (30 B) copied, 6.15348 s, 0.0 kB/s
在这种情况下sed
,块缓冲区不会写入任何内容,dd
直到其输入关闭,此时它会刷新其输出并退出。事实上,当它的编写者在这两种情况下退出时,它就会退出,正如在dd
管道末尾写入的诊断中所见证的那样。
尽管如此...
( cd /tmp; c=
mkfifo i o
dd bs=1 <o&
cat >o <i&
while sleep 1
do [ -z "$c" ] && rm [io]
[ "$c" = 5 ] && exit
date "+%S:%t$((c+=1))"
done| tee i
)
40: 1
40: 1
41: 2
41: 2
42: 3
42: 3
43: 4
43: 4
44: 5
44: 5
30+0 records in
30+0 records out
30 bytes (30 B) copied, 6.14734 s, 0.0 kB/s
现在我的cat
是 GNU 版本 - GNU 的cat
(如果没有选项调用)从不块缓冲区。如果您也使用 GNU,cat
那么对我来说很明显问题不在于您的中继,而在于您的 Java 程序。但是,如果您是不是使用 GNU cat
,那么它有可能缓冲输出。不过,你很幸运,只有一个POSIX 规范选项cat
,这适用于-u
nbuffered 输出。你可以尝试一下。
我正在查看你的东西,在玩了一段时间后,我很确定你的问题是一个僵局。cat
最后,你的输入挂在了那里,如果 JVM 进程也在等待有人与之对话,那么可能什么也不会发生。所以我写了这个:
#!/bin/sh
die() for io in i o e
do rm "$io"
kill -9 "$(($io))"
done 2>/dev/null
io() while eval "exec $((fd+=1))>&-"
do [ "$fd" = 9 ] &&
{ cat; kill -1 0; }
done
cd /tmp; fd=1
mkfifo i o e
{ io <o >&4 & o=$!
io <e >&5 & e=$!
io >i <&3 & i=$!
} 3<&0 4>&1 5>&2
trap "die; exit 0" 1
echo; wait
不幸的是,处理返回码有点草率。不过,通过更多的工作,可以使其可靠地做到这一点。无论如何,正如你所看到的背景全部cat
我认为,这应该会引发wait
一条cat
链,在所有情况下都会杀死所有这些。
答案3
在 bash 中,你可以尝试:
forward() { while read line; do echo "$line"; done; }
forward </tmp/out &
forward </tmp/err >&2 &
forward >/tmp/in
wait
然后使用 运行脚本stdbuf -i0 -oL
。
该forward
函数基本上是pipe
python 代码中的方法,其中 src 和 dest 默认为 stdin 和 stdout,并且没有显式刷新,希望stdbuf
可以做到这一点。
如果您已经关心性能并希望将其放在 C 代码中,请将其放在 C 代码中。我不熟悉 stdbuf 友好的cat
替代方案,但这里有一个 C++ 单行(几乎)用于cat
ting :stdin
stdout
#include <iostream>
using namespace std;
int main() { for(string line; getline(cin,line); ){ cout<<line<<'\n'; }; }
或者,如果您绝对必须有 C 代码:
#include <stdlib.h>
#include <stdio.h>
int main()
{
const size_t N = 80;
char *lineptr = (char*)malloc(N); //this will get realloced if a longer line is encountered
size_t length = N;
while(getline(&lineptr, &length, stdin) != -1){
fputs(lineptr, stdout);
}
free(lineptr);
return 0;
}
C 和 C++ 示例都没有在每行之后进行显式刷新,因为我认为将其保留为stdbuf
更好的设计决策,但您始终可以通过fflush(stdout)
在每行之后调用 C 示例来添加它,在 C++ 示例中替换'\n'
为endl
,或者通过在这两种情况下将缓冲预先设置为行缓冲,可以更有效地实现,这样您就不必进行那些“昂贵的”C/C++ 函数调用。