[英]Python subprocess: Giving stdin, reading stdout, then giving more stdin
我正在使用一款名為 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”,那么我傳遞“yes”
到目前為止,這是我能得到的最接近的:
#!/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 的一些實際原因。
謝謝你的時間!
你不需要在你的例子中使用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 下讀取非阻塞,這個答案可能會有所幫助。
[1]來自python 2 文檔:
讀線(限制=-1)
從流中讀取並返回一行。 如果指定了限制,最多將讀取限制字節。
對於二進制文件,行終止符始終為 b'\n'; 對於文本文件,open() 的換行參數可用於選擇可識別的行終止符。
[2]來自man 2 read
:
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count);
[3]例子
$ tree
.
├── prog.py
└── prog.sh
程序文件
#!/usr/bin/env bash
for i in $(seq 3); do
echo "${RANDOM}"
sleep 1
done
sleep 3
echo "${RANDOM}"
程序.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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.