我正在使用一款名为Chimera的科学软件。对于此问题下游的一些代码,它要求我使用Python 2.7。
我想调用一个进程,为该进程提供一些输入,读取它的输出,并基于此提供更多的输入,等等。
我使用Popen
打开进程,使用process.stdin.write
传递标准输入,但是后来我陷入了尝试在进程仍在运行时获取输出的问题。 process.communicate()
停止了该过程,process.stdout.readline()
似乎让我陷入了无限循环。
以下是我要执行的操作的简化示例:
假设我有一个名为exampleInput.sh
的bash脚本。
#!/bin/bash
# exampleInput.sh
# Read a number from the input
read -p 'Enter a number: ' num
# Multiply the number by 5
ans1=$( expr $num \* 5 )
# Give the user the multiplied number
echo $ans1
# Ask the user whether they want to keep going
read -p 'Based on the previous output, would you like to continue? ' doContinue
if [ $doContinue == "yes" ]
then
echo "Okay, moving on..."
# [...] more code here [...]
else
exit 0
fi
通过命令行与此交互,我将运行脚本,键入“ 5”,然后,如果返回“ 25”,则键入“ yes”,如果不是,则键入“ no “。
我想运行一个python脚本,并在其中传递exampleInput.sh
“ 5”,如果返回“ 25”,那么我传递“是”
到目前为止,这与我所能达到的尽可能接近:
#!/home/user/miniconda3/bin/python2
# talk_with_example_input.py
import subprocess
process = subprocess.Popen(["./exampleInput.sh"],
stdin = subprocess.PIPE,
stdout = subprocess.PIPE)
process.stdin.write("5")
answer = process.communicate()[0]
if answer == "25":
process.stdin.write("yes")
## I'd like to print the STDOUT here, but the process is already terminated
但这当然失败了,因为在`process.communicate()'之后,我的进程不再运行了。
(以防万一/仅供参考):实际问题
Chimera通常是基于gui的应用程序,用于检查蛋白质结构。如果您运行chimera --nogui
,它将打开一个提示并接受输入。
在运行下一个命令之前,我经常需要知道嵌合体的输出。例如,我经常会尝试生成蛋白质表面,如果Chimera无法生成表面,则它不会破裂-只是通过STDOUT如此说。因此,在我的python脚本中,当我遍历许多蛋白质进行分析时,我需要检查STDOUT以了解是否继续对该蛋白质进行分析。
在其他用例中,我将先通过Chimera运行许多命令来清理蛋白质,然后再运行大量单独的命令来获取不同的数据,并使用该数据来确定是否运行其他命令。我可以获取数据,关闭子进程,然后运行另一个进程,但这将需要每次重新运行所有这些清理命令。
无论如何,这些是现实世界中为什么我希望能够将STDIN推送到子进程,读取STDOUT并仍然能够推送更多STDIN的一些原因。
感谢您的时间!
答案 0 :(得分:1)
您无需在示例中使用process.communicate
。
只需使用process.stdin.write
和process.stdout.read
进行读写。另外,请确保发送换行符,否则read
将不会返回。而且,当您从标准输入中读取内容时,还必须处理来自echo
的换行符。
注意:process.stdout.read
将一直阻塞到EOF
。
# talk_with_example_input.py
import subprocess
process = subprocess.Popen(["./exampleInput.sh"],
stdin = subprocess.PIPE,
stdout = subprocess.PIPE)
process.stdin.write("5\n")
stdout = process.stdout.readline()
print(stdout)
if stdout == "25\n":
process.stdin.write("yes\n")
print(process.stdout.readline())
$ python2 test.py
25
Okay, moving on...
以这种方式与程序通信时,您应特别注意应用程序实际编写的内容。最好的方法是在十六进制编辑器中分析输出:
$ chimera --nogui 2>&1 | hexdump -C
请注意,readline
[1] 仅读取到下一个换行符(\n
)。对于您的情况,您必须至少调用四次readline
才能获得第一个输出块。
如果您只想读取所有内容,直到子进程停止打印,则必须逐字节读取并实施超时。遗憾的是,read
和readline
都没有提供这种超时机制。这可能是因为基础read
系统调用 [2] (Linux)也不提供。
在Linux上,我们可以使用poll / select编写单线程read_with_timeout()
。有关示例,请参见 [3] 。
from select import epoll, EPOLLIN
def read_with_timeout(fd, timeout__s):
"""Reads from fd until there is no new data for at least timeout__s seconds.
This only works on linux > 2.5.44.
"""
buf = []
e = epoll()
e.register(fd, EPOLLIN)
while True:
ret = e.poll(timeout__s)
if not ret or ret[0][1] is not EPOLLIN:
break
buf.append(
fd.read(1)
)
return ''.join(buf)
如果您需要一种可靠的方式来读取Windows和Linux下的非阻塞,this answer might be helpful。
[1] :
来自readline(limit = -1)
从流中读取并返回一行。如果指定了限制,则最多将读取限制字节。
对于二进制文件,行终止符始终为b'\ n';对于文本文件,可以使用open()的newline参数来选择识别的行终止符。
man 2 read
的[2] :
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count);
[3] 示例
$ tree
.
├── prog.py
└── prog.sh
prog.sh
#!/usr/bin/env bash
for i in $(seq 3); do
echo "${RANDOM}"
sleep 1
done
sleep 3
echo "${RANDOM}"
prog.py
# talk_with_example_input.py
import subprocess
from select import epoll, EPOLLIN
def read_with_timeout(fd, timeout__s):
"""Reads from f until there is no new data for at least timeout__s seconds.
This only works on linux > 2.5.44.
"""
buf = []
e = epoll()
e.register(fd, EPOLLIN)
while True:
ret = e.poll(timeout__s)
if not ret or ret[0][1] is not EPOLLIN:
break
buf.append(
fd.read(1)
)
return ''.join(buf)
process = subprocess.Popen(
["./prog.sh"],
stdin = subprocess.PIPE,
stdout = subprocess.PIPE
)
print(read_with_timeout(process.stdout, 1.5))
print('-----')
print(read_with_timeout(process.stdout, 3))
$ python2 prog.py
6194
14508
11293
-----
10506