Saya ingin e. g. baca baris pertama yang dicetak oleh "tcpdump":

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

Menggunakan "ptyprocess" (konteks: proses lokal, terminal terlibat) dan pilih () untuk menunggu data baru dengan batas waktu:

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")

Untuk Python 3.x (diuji dengan 3.6.10 dan 3.8.1) kode ini membaca baris yang disebutkan di atas yang dicetak oleh "tcpdump".

Untuk Python 2.x (diuji dengan 2.7.17) kode ini hanya membaca karakter pertama "t" dan setelah itu select() time out. Saya juga mengamati, bahwa untuk putaran pertama, lebih dari satu karakter dibaca, tetapi tidak semua.

Diuji pada Debian 10.

Bagaimana saya bisa menggunakan select() dengan batas waktu (atau yang serupa) dengan "ptyprocess" untuk menunggu data baru, sebelum saya membaca karakter berikutnya di Python 2?

Pembaruan 1:

Strace menunjukkan perbedaan berikut:

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

Saya. untuk Python 2, read(..., 8192) dipanggil dan untuk Python 3, read(..., 1). Bagaimana saya bisa mencapai, bahwa untuk Python 2 juga read(..., 1) dipanggil?

Pembaruan 2:

Masalahnya independen dari "tcpdump" dan juga dapat direproduksi seperti ini:

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")

Keluaran 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

Keluaran 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
1
python_user_1234 23 April 2020, 00:49

1 menjawab

Jawaban Terbaik

PtyProcess.read() memanggil self.fileobj.read1(). PtyProcess.fileobj memiliki tipe BufferedRWPair. BufferedRWPair.read1() mendelegasikan ke BufferedRWPair.reader.read1(). Konstruktor BufferedRWPair membuat objek BufferedReader dari parameter reader.

Dalam Python 2.7.16 Modules/_io/bufferedio.c/buffered_read1() memanggil _bufferedreader_fill_buffer(self), yang melakukan:

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

Dalam Python 3.8.1 Modules/_io/bufferedio.c/_io__Buffered_read1_impl() panggilan:

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

Dengan kata lain, di Python 3 BufferedReader.read1(n) raw-reads n byte, sedangkan di Python 2 membaca lebih banyak byte untuk mengisi buffer.

Tidak mungkin menggunakan read(1), yang berfungsi pada buffer, dalam kombinasi dengan select(), yang berfungsi pada deskriptor file yang mendasarinya, seperti yang dilakukan kode yang diposting dalam pertanyaan.

Kode berikut, yang menggunakan pexpect alih-alih ptyprocess, memungkinkan untuk membaca dengan batas waktu:

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

Keluaran:

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
1
Błażej Michalik 27 April 2020, 11:06