Просмотр stdout подпроцесса. Открытые объекты ведут себя неправильно, я что-то упустил?

Точнее, он не обновляется до тех пор, пока все, что он содержит, не будет прочитано (но только если поток был прочитан хотя бы один раз), что делает его фактически дисфункциональным.

Простите за странный пример, но в настоящее время я пытаюсь написать простой графический монитор ping:

import tkinter as tk

from subprocess import Popen, PIPE, STDOUT
import shlex, re
from sys import stdout, platform

class Ping(object):
    def __init__(self):
        if platform == "win32":
            command = shlex.split("ping -w 999 -t 8.8.8.8")
        elif platform == "linux" or platform == "osx":
            command = shlex.split("ping -W 1 8.8.8.8")
        self.ping = Popen(command, stdout=PIPE, stderr=STDOUT, shell=True)
        self.ping.stdout.readline()
        self.ping.stdout.readline()
    def get_next_ping(self):
        has_line = str.find(self.ping.stdout.peek().decode("ascii", "ignore"), "\n") != -1
        if not has_line:
            print(self.ping.stdout.peek()) # Debug statement
            return None
        else:
            line = self.ping.stdout.readline().decode("ascii", "ignore")
            print(line) # Debug statement
            try: return int(float(re.findall("([0-9]+)[^m]?ms", line)[0]))
            except IndexError: return -1

class App(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.pingmon = Ping()
        self.bind("<ButtonPress-1>", self.check_buffer)

    def check_buffer(self, event):
        print(self.pingmon.get_next_ping())
app=App()
app.mainloop()

В этом примере, когда вы нажимаете, подпроцесс опрашивается, чтобы узнать, доступна ли новая строка (содержащая выходной пинг или сообщение о тайм-ауте). Если вы запустите проект и начнете щелкать немедленно, вы заметите, что вывод peek() прекратил обновление и всегда b'Reply from 8.8.8.8: ',

Я также попробовал альтернативный метод проверки длины peekЭто результат, но он, очевидно, никогда не равен нулю, так что это тоже ничего не стоит.

Далее я попытался вызвать flush() метод потока, но он, похоже, никак не помогает ситуации

Конечный результат заключается в том, что subprocess.Popen.stdout.peek() кажется неработоспособным и не пригодным для использования по назначению - заглянуть в выходной буфер, но Python - это зрелый язык, и я не ожидал бы найти в нем такого рода ошибку, есть ли что-то, чего мне не хватает? Если нет, как я могу обойти эту проблему?

2 ответа

Ответ

просто используйте readline() метод. если строка не существует, он возвращает пустой объект байтов - b''

пример использования readline():

from subprocess import Popen, PIPE, STDOUT

curdir = Popen(['pwd'], stdout=PIPE, stderr=STDOUT)
print(curdir.stdout.readline())
print(curdir.stdout.readline())
print(curdir.stdout.readline())

это выведет (на python3):

b'/home/shmulik\n'
b''
b''

для вашего случая это обновляется get_next_ping() func (также немного изменил регулярное выражение)

def get_next_ping(self):
    line = self.ping.stdout.readline()
    if not line:
        return

    line = line.decode('utf-8', 'ignore')
    print(line)  # Debug statement
    try:
        return int(float(re.search(r'([0-9.]+)[^m]?ms', line).group(1)))
    except (IndexError, AttributeError):
        return -1

Неблокируемому

если вам небезразлична блокирующая операция, посмотрите на это, так что ответьте

Вы можете использовать select модуль в unix для чтения из stdout неблокирующим способом или запуска фонового потока для обновления буфера для чтения.

Пример использования потока для буферизации

class Ping(object):
    def __init__(self):
        if platform == "win32":
            command = shlex.split("ping -w 999 -t 8.8.8.8")
        elif platform == "linux" or platform == "osx":
            command = shlex.split("ping -W 1 8.8.8.8")
        self.ping = Popen(command, stdout=PIPE, stderr=STDOUT, shell=True)
        self.ping.stdout.readline()
        self.ping.stdout.readline()

        self.lines = []  # lines will be added here by the background thread
        self.lines_lock = Lock()  # avoid race conditions on .pop()
        self.lines_reader_thread = Thread(target=self._readlines)  # start the background thread
        self.lines_reader_thread.daemon = True
        self.lines_reader_thread.start()

    def _readlines(self):
        line = None

        while line or line is None:
            line = self.ping.stdout.readline().decode()
            with self.lines_lock:
                self.lines.append(line)

    def get_next_ping(self):
        with self.lines_lock:
            if not self.lines:
                return
            line = self.lines.pop()

        print(line)  # Debug statement
        try:
            return int(float(re.search(r'([0-9.]+)[^m]?ms', line).group(1)))
        except (IndexError, AttributeError):
            return -1

Предложения

  1. используйте существующую библиотеку Python для проверки связи вместо анализа stdout. некоторым библиотекам требуется запускать с правами root под linux, это может быть ограничением для вас.
  2. отправлять один пинг за раз вместо продолжительного фонового процесса пинга. таким образом, вы можете использовать subprocess.check_output(),
  3. избегать использования shell=True на popen() Передача в него неанизированного ввода может привести к внедрению команды.

@Llamageddon Я думаю, что указатель файла должен быть перемещен, чтобы обновить буфер в проверке if not has_line, используя readline(), Peek не продвигает указатель, поэтому вы, по сути, получили ошибку, которая будет "пиковаться" в пустом буфере файлов.

if not has_line:
    print(self.ping.stdout.peek()) # Debug statement

    self.ping.stdout.readline() # Should refresh the filebuffer.

    return None

Re: peek() может использоваться для просмотра больших файловых буферов и, возможно, не подходит для вашей работы, учитывая размер ответа; Тем не менее, я думаю, что хороший пример, когда peek() не является "дисфункциональным и не пригодным для использования":), когда строка в буфере имеет длину 100 000 символов, и просмотра первых 100 символов будет достаточно, чтобы оценить, что делать со строкой (т.е. пропустить ее или применить дополнительную логику). Пик позволил бы нам выполнить просмотр и оценку всего, минимизируя время блока.

Другие вопросы по тегам