Python:subprocess.stdin.write 无法正常工作

问题描述 投票:0回答:4

我是Python新手,但我可以编码和调试一些。在过去的几天里,以下问题一直困扰着我,并寻求答案。任何帮助深表感谢。 任务:我想做一个交互式 telnet(我知道有一个 telnet 库,但由于各种原因我们没有使用它)。 为此,我使用 subprocess.popen

p = subprocess.Popen(telnet_command,
                         stdin = subprocess.PIPE,
                         stdout = outputfileobj,
                         stderr = errorfileobj)

我执行 poll() 来查看服务器是否已连接到会话。验证后,我继续写入标准输入以进行交互式通信。

inputTxt = 'GET / HTTP/1.1\nHost: ' + hostheader + '\n\n'
p.stdin.write(inputTxt)
p.stdin.flush()

这就是问题发生的地方。我得到 http 响应(或至少输出)5/6 次,但 1/6 次,我没有得到输出,子进程被终止 - 这是不可能的。

我对失败案例进行了系统跟踪,请在下面找到相同的内容。

16129 write(7, "GET / HTTP/1.1\nHost: www.google."..., 37) = 37
16129 gettimeofday({1310538497, 134474}, NULL) = 0
16129 futex(0x81993a8, FUTEX_WAKE, 1)   = 0
16129 stat64("/etc/localtime", {st_mode=S_IFREG|0644, st_size=56, ...}) = 0
16129 fstat64(4, {st_mode=S_IFREG|0644, st_size=520689, ...}) = 0
16129 _llseek(4, 520689, [520689], SEEK_SET) = 0
16129 stat64("/etc/localtime", {st_mode=S_IFREG|0644, st_size=56, ...}) = 0
16129 write(4, "2011-07-13 06:28:17,134 : pconns"..., 170) = 170
16129 futex(0x81d3b30, FUTEX_WAKE, 1)   = 0
16129 waitpid(16198, 0xffa945e8, WNOHANG) = 0
16129 gettimeofday({1310538497, 135160}, NULL) = 0
16129 fstat64(6, {st_mode=S_IFREG|0644, st_size=81, ...}) = 0
16129 fstat64(6, {st_mode=S_IFREG|0644, st_size=81, ...}) = 0
16129 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xf74b9000
16129 _llseek(6, 0, [0], SEEK_SET)      = 0
16129 fstat64(6, {st_mode=S_IFREG|0644, st_size=81, ...}) = 0
16129 _llseek(6, 0, [0], SEEK_CUR)      = 0
16129 read(6, "Trying 74.125.236.48...\nConnecte"..., 4096) = 81
16129 read(6, "", 4096)                 = 0
16129 close(6)                          = 0
16129 munmap(0xf74b9000, 4096)          = 0
16129 gettimeofday({1310538497, 135778}, NULL) = 0
16129 futex(0x81993a8, FUTEX_WAKE, 1)   = 0
16198 <... select resumed> )            = 1 (in [0])
16198 read(0, "GET / HTTP/1.1\nHost: www.google."..., 8191) = 37
16129 stat64("/etc/localtime",  <unfinished ...>
16198 ioctl(1, TCFLSH, 0)               = -1 ENOTTY (Inappropriate ioctl for device)
16129 <... stat64 resumed> {st_mode=S_IFREG|0644, st_size=56, ...}) = 0
16198 select(4, [0 3], [3], [3], {0, 0}) = 1 (out [3], left {0, 0})
16129 fstat64(4, {st_mode=S_IFREG|0644, st_size=520859, ...}) = 0
16198 send(3, "GET / HTTP/1.1\r\nHos\377\363\377\375\6", 24, 0 <unfinished ...>
16129 _llseek(4, 520859,  <unfinished ...>
16198 <... send resumed> )              = 24
16198 select(4, [0 3], [3], [3], {0, 0} <unfinished ...>
16129 <... _llseek resumed> [520859], SEEK_SET) = 0
16198 <... select resumed> )            = 1 (out [3], left {0, 0})
16198 send(3, ": www.google.com\r\n\r\n", 20, 0 <unfinished ...>
16129 stat64("/etc/localtime",  <unfinished ...>
16198 <... send resumed> )              = 20

如果仔细查看行 16198 send(3, "GET / HTTP/1.1 Hos���� ", 24, 0 在跟踪中,字符串“Host”被一些“Hos���� ”替换。我不知道为什么这种情况会在同时这也会关闭我建立的telnet连接。如果您需要更多数据,请告诉我。

python subprocess stdin
4个回答
1
投票

如果您尝试使用

communicate()
方法而不是使用 stdin 将数据发送到子进程,会怎样,因为 Python 文档不鼓励后者:

警告:使用communicate()而不是.stdin.write、.stdout.read或 .stderr.read 以避免由于任何其他操作系统管道而导致的死锁 缓冲区填满并阻塞子进程。

试试这个:

p.communicate(input='GET / HTTP/1.1\nHost: ' + hostheader + '\n\n')

1
投票

虽然 communicate 更好,但如果您确实必须使用 stdin.write,您可以使用以下方法使调用非阻塞:

fcntl.fcntl(p.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)

为了更好地理解,请阅读以下内容:

io非阻塞


0
投票

如果不需要交互,我不使用communicate,而是提前将文本放入管道中。 我还没有找到更Pythonic的“text_to_stream”。

import os
import sys
import subprocess


def text_to_stream(text):
    p = os.pipe()
    os.write(p[1], text.encode('utf-8'))
    os.close(p[1])
    return os.fdopen(p[0], "r")


def run(cmd, stdin_text=None):
    stdin = text_to_stream(stdin_text) if stdin_text else sys.stdin
    result = subprocess.run(
        cmd, stdout=sys.stdout, stderr=sys.stderr, stdin=stdin)
    result.check_returncode()

0
投票

这是为那些真正想要解决所写问题的人提供的答案——当你需要交互式输出时(指的是我五分钟前)——特别是当

communicate()
不是正确答案时,因为你需要提供多个输入块。

在下面的程序中,我将孩子的标准输出设置为非阻塞(对于标准错误可能也是必要的,具体取决于用例)以允许我阅读。另一种选择可能是查看底层 fd 是否可以通过

select
或朋友读取。

然后我写入孩子的标准输入;并进行显式刷新以确保它消失,然后检查是否有任何东西返回。在此特定示例中,第一次读取返回 None,然后该返回后的每次读取都是前一个周期的(转换后的)写入数据。

显然,让孩子不将其输出缓冲到无穷大是留给读者的练习。我通过使用

unbuffer
程序对这个玩具示例进行了破解,但这并不适合所有示例。但是,如果发送大量数据,请注意死锁;在写入整个缓冲区之前,您可能必须读取部分结果(这可能需要将 sp.stdin 设置为非阻塞,或者通常需要更复杂的 I/O 处理)。

当子进程终止时,

flush()
得到
BrokenPipeError
异常(在我的示例中,另一种可能性是在
write()
中得到该异常。

虽然它不适合我的用例,但如果您正在做一个真正的交互式应用程序,需要对孩子所做的特定事情做出响应,那么使用 pexpect 模块可能是正确的答案。

import sys
import os
import subprocess
import time
import fcntl

sp = subprocess.Popen(["timeout", "2", "unbuffer", "-p", "tr", ".", "-"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
fl = fcntl.fcntl(sp.stdout, fcntl.F_GETFL)
fcntl.fcntl(sp.stdout, fcntl.F_SETFL, fl | os.O_NONBLOCK)
while True:
    val = str(time.time()) + "\n"
    x = sp.stdin.write(val.encode("utf-8"))
    sp.stdin.flush()
    y = sp.stdout.read(1024)
    print(f"Wrote {x} bytes of {val[:-1]}, got {y}")
    time.sleep(0.4)
© www.soinside.com 2019 - 2024. All rights reserved.