簡體   English   中英

Python 子進程:提供標准輸入,讀取標准輸出,然后提供更多標准輸入

[英]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.writeprocess.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至少四次才能獲得第一個輸出塊。

如果您只想讀取所有內容直到子進程停止打印,則必須逐字節讀取並實現超時。 可悲的是, readreadline都沒有提供這樣的超時機制。 這可能是因為底層的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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM