Невозможно прочитать последнюю небуферизованную строку

Я пытаюсь прочитать последнюю строку из команды, например "apt-get download firefox". Обычно выход будет выглядеть как

Get:1 http://archive.ubuntu.com/ubuntu/ utopic/main firefox amd64 32.0+build1-0ubuntu2 [34.9 MB]
2% [1 firefox 646 kB/34.9 MB 2%]

с обновлением последней строки (она не пишет новую строку, пока не достигнет 100%). Моя цель - теперь прочитать прогресс в реальном времени. Вот мой текущий пример кода:

#!/usr/bin/python3 -u
# coding=utf-8

import subprocess, sys

pipe = subprocess.Popen(['apt-get', 'download', 'firefox'], 0, stderr = subprocess.PIPE, stdout = subprocess.PIPE)
while True:
    content = pipe.stdout.read(1).decode()
    if content == '':
        break
    sys.stdout.write(content)
    sys.stdout.flush()
pipe.wait()

Я отключил буферизацию вывода для вызова подпроцесса, а также для двоичного вывода для процесса Python (с аргументом -u). Но я получаю только первую строку, но не прогресс второй линии. Кто-нибудь знает, как я могу это достичь?

Ответ 1

Если stdout из apt-get перенаправляется в канал, например,

$ apt-get download firefox | cat

то он не сообщает о прогрессе (последняя строка, например, 2% [1 firefox 646 kB/34.9 MB 2%] будет не на выходе). stdout=subprocess.PIPE естественно создает трубу; поэтому apt-get не печатает ход загрузки в вашем случае.

Если вы хотите как захватить вывод apt-get, так и увидеть его на экране в режиме реального времени с последней строкой (отчет о проделанной работе), тогда вы можете использовать pexpect чтобы обмануть дочерний процесс, подумав, что он работает в терминале:

import sys
import pexpect # $ pip install pexpect

output, exitstatus = pexpect.runu('apt-get download firefox',
                                  logfile=sys.stdout,
                                  withexitstatus=1)

Вы можете сделать то же самое, используя только модуль stdlib pty:

#!/usr/bin/env python3
import os
import pty

output = []
def read(fd):
    data = os.read(fd, 1024)
    output.append(data)
    return data
status = pty.spawn(['apt-get', 'download', 'firefox'], read)

@eryksun на Python Отслеживание проблем apt-get --quiet опция:

#!/usr/bin/env python3
import shlex
from io import TextIOWrapper
from subprocess import Popen, PIPE

output = []
with Popen(shlex.split("apt-get --quiet=0 download firefox"),
           stdout=PIPE, bufsize=1) as p:
    # recognize '\r' as newline but don't convert it to '\n'
    for line in TextIOWrapper(p.stdout, newline=''):
        print(line, end='', flush=True) # print to terminal
        output.append(line) # save for later
print(p.returncode)