【问题标题】:read with timeout from local process in pseudo terminal从伪终端中的本地进程超时读取
【发布时间】:2020-04-22 21:49:07
【问题描述】:

我想。 G。读取“tcpdump”打印出来的第一行:

tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes

使用“ptyprocess”(上下文:本地进程,涉及终端)和select()超时等待新数据:

import logging
from ptyprocess import PtyProcess
from select import select

logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s %(name)s %(message)s")

pty_process = PtyProcess.spawn(
    argv=["sudo", "tcpdump", "-w", "capture.pcap", "-i", "enp0s3"],
    echo=True)
while True:
    rlist, _, _ = select([pty_process.fd], [], [], 1)
    if pty_process.fd in rlist:
        try:
            data = pty_process.read(1)
        except EOFError:
            logging.debug("EOF")
            break
        logging.debug("read: %r", data)
    else:
        logging.debug("timeout")

对于 Python 3.x(使用 3.6.10 和 3.8.1 测试),此代码读取由“tcpdump”打印的上述行。

对于 Python 2.x(使用 2.7.17 测试),此代码仅读取第一个字符“t”,然后 select() 超时。我还观察到,第一次运行时,读取了多个字符,但不是全部。

在 Debian 10 上测试。

在读取 Python 2 中的下一个字符之前,如何使用带有“ptyprocess”超时(或类似内容)的 select() 来等待新数据?

更新 1:

strace 显示以下区别:

Python 2:

select(6, [5], [], [], {tv_sec=1, tv_usec=0}) = 1 (in [5], left {tv_sec=0, tv_usec=999993})
read(5, "tcpdump: listening on enp0s3, li"..., 8192) = 86

Python 3:

select(6, [5], [], [], {tv_sec=1, tv_usec=0}) = 1 (in [5], left {tv_sec=0, tv_usec=999994})
read(5, "t", 1)                         = 1

我。 e.对于 Python 2,调用 read(..., 8192),对于 Python 3,调用 read(..., 1)。我怎样才能实现,对于 Python 2 也调用 read(..., 1) ?

更新 2:

问题与“tcpdump”无关,也可以这样重现:

import logging
from ptyprocess import PtyProcess
from select import select

logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s %(name)s %(message)s")

pty_process = PtyProcess.spawn(
    argv=["bash", "-c", "echo 123 ; sleep 3"],
    echo=True)
while True:
    rlist, _, _ = select([pty_process.fd], [], [], 1)
    if pty_process.fd in rlist:
        try:
            data = pty_process.read(1)
        except EOFError:
            logging.debug("EOF")
            break
        logging.debug("read: %r", data)
    else:
        logging.debug("timeout")

Python 2 输出:

2020-04-23 12:51:27,126 root read: '1'
2020-04-23 12:51:28,193 root timeout
2020-04-23 12:51:29,204 root timeout
2020-04-23 12:51:30,129 root read: '2'
2020-04-23 12:51:30,129 root read: '3'
2020-04-23 12:51:30,129 root read: '\r'
2020-04-23 12:51:30,130 root read: '\n'
2020-04-23 12:51:30,130 root EOF

Python 3 输出:

2020-04-23 12:51:23,106 root read: b'1'
2020-04-23 12:51:23,107 root read: b'2'
2020-04-23 12:51:23,107 root read: b'3'
2020-04-23 12:51:23,107 root read: b'\r'
2020-04-23 12:51:23,107 root read: b'\n'
2020-04-23 12:51:24,109 root timeout
2020-04-23 12:51:25,109 root timeout
2020-04-23 12:51:26,109 root EOF

【问题讨论】:

  • 这能回答你的问题吗? Handling tcpdump output in python
  • 不管这个问题是否回答了这个问题,使用 Popentcpdump -l 在两个 Python 版本上都有效,然后如果进入下一个迭代需要超过一秒的时间,您可以终止它在循环中(见答案)。与 python 中的许多事情一样,有多种方法可以做事情,我认为在这种情况下 Popen 更可取。
  • @RossJacobs 我想逐个字符地读取超时,例如。 G。能够执行诸如“读取直到出现超时的特定字符串,稍后继续读取”之类的操作。在这个问题中,我想关注“ptyprocess”(即涉及终端的情况,尽管这里的 tcpdump 可能不需要它)而不是我用于其他情况的“子进程”。
  • 您能否在您的问题中更详细地说明为什么此处需要 ptyprocess 以及更广泛的环境上下文(例如,这是在 SSH 连接上并且必须通过伪终端运行)?
  • @RossJacobs 实际上这个问题发生在使用以下 4 种情况的类库开发过程中:1)本地进程,无终端,2)本地进程,终端,3)SSH,无终端,4 ) SSH,终端。这里我们说的是案例2)。

标签: python select timeout pexpect pty


【解决方案1】:

PtyProcess.read() 呼叫self.fileobj.read1()PtyProcess.fileobj 的类型为 BufferedRWPairBufferedRWPair.read1() 代表BufferedRWPair.reader.read1()BufferedRWPair 的构造函数根据参数reader 创建一个BufferedReader 对象。

在 Python 2.7.16 中,Modules/_io/bufferedio.c/buffered_read1() 调用 _bufferedreader_fill_buffer(self),它会:

len = self->buffer_size - start;
n = _bufferedreader_raw_read(self, self->buffer + start, len);

在 Python 3.8.1 中 Modules/_io/bufferedio.c/_io__Buffered_read1_impl() 调用:

r = _bufferedreader_raw_read(self, PyBytes_AS_STRING(res), n);

换句话说,在 Python 3 中BufferedReader.read1(n) raw-读取 n 个字节,而在 Python 2 中它读取更多字节来填充缓冲区。

不能像问题中发布的代码那样,将作用于缓冲区的 read(1) 与作用于底层文件描述符的 select() 结合使用。

以下代码使用 pexpect 而不是 ptyprocess,允许读取超时:

import logging
import pexpect

logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s %(name)s %(message)s")

child = pexpect.spawn("bash -c 'echo 123 ; sleep 3'")
while True:
    try:
        data = child.read_nonblocking(size=1, timeout=1)
        logging.debug("read: %r", data)
    except pexpect.TIMEOUT:
        logging.debug("timeout")
    except pexpect.EOF:
        logging.debug("EOF")
        break

输出:

2020-04-26 14:54:56,006 root read: '1'
2020-04-26 14:54:56,007 root read: '2'
2020-04-26 14:54:56,007 root read: '3'
2020-04-26 14:54:56,007 root read: '\r'
2020-04-26 14:54:56,007 root read: '\n'
2020-04-26 14:54:57,009 root timeout
2020-04-26 14:54:58,010 root timeout
2020-04-26 14:54:59,008 root EOF

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多