Python 3.4.3 subprocess.Popen получить вывод команды без трубопроводов?

Я пытаюсь назначить вывод команды переменной без команды, думающей, что она передается по трубопроводу. Причиной этого является то, что данная команда дает неформатированный текст в качестве вывода, если он передается по каналам, но он придает цветному форматированному тексту, если он запускается с терминала. Мне нужно получить этот отформатированный текст.

До сих пор я пробовал несколько вещей. Я пробовал Popen так:

output = subprocess.Popen(command, stdout=subprocess.PIPE)
output = output.communicate()[0]
output = output.decode()
print(output)

Это позволит мне распечатать вывод, но он дает мне неформатированный вывод, который я получаю при передаче команды. Это имеет смысл, поскольку я пишу его здесь в коде Python. Но мне любопытно, есть ли способ назначить вывод этой команды непосредственно переменной без команды, запускающей собственную версию.

Я также попробовал следующую версию, которая вместо этого использует check_output:

output = subprocess.check_output(command)
output = output.decode()
print(output)

И снова я получаю тот же неформатированный вывод, который команда возвращает, когда команда передана по протоколу.

Есть ли способ получить отформатированный вывод, вывод, который команда обычно выдает из терминала, когда он не передается по каналам?

Ответ 1

Используя pexpect:

2.py:

import sys

if sys.stdout.isatty():
    print('hello')
else:
    print('goodbye')

подпроцесс:

import subprocess

p = subprocess.Popen(
    ['python3.4', '2.py'],
    stdout=subprocess.PIPE
)

print(p.stdout.read())

--output:--
goodbye

pexpect:

import pexpect

child = pexpect.spawn('python3.4 2.py')

child.expect(pexpect.EOF)
print(child.before)  #Print all the output before the expectation.

--output:--
hello

Здесь это с grep --colour=auto:

import subprocess

p = subprocess.Popen(
    ['grep', '--colour=auto', 'hello', 'data.txt'],
    stdout=subprocess.PIPE
)

print(p.stdout.read())

import pexpect

child = pexpect.spawn('grep --colour=auto hello data.txt')
child.expect(pexpect.EOF)
print(child.before)

--output:--
b'hello world\n'
b'\x1b[01;31mhello\x1b[00m world\r\n'

Ответ 2

Да, вы можете использовать pty module.

>>> import subprocess
>>> p = subprocess.Popen(["ls", "--color=auto"], stdout=subprocess.PIPE)
>>> p.communicate()[0]
# Output does not appear in colour

С pty:

import subprocess
import pty
import os

master, slave = pty.openpty()
p = subprocess.Popen(["ls", "--color=auto"], stdout=slave)
p.communicate()
print(os.read(master, 100)) # Print 100 bytes
# Prints with colour formatting info

Примечание из документов:

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

Менее красивый способ чтения всего вывода до конца за один раз:

def num_bytes_readable(fd):
    import array
    import fcntl
    import termios
    buf = array.array('i', [0])
    if fcntl.ioctl(fd, termios.FIONREAD, buf, 1) == -1:
        raise Exception("We really should have had data")
    return buf[0]

print(os.read(master, num_bytes_readable(master)))

Изменить: лучший способ получить контент сразу благодаря @Antti Haapala:

os.close(slave)
f = os.fdopen(master)
print(f.read())

Изменить: люди правы, чтобы указать, что это затормозит, если процесс генерирует большой вывод, поэтому лучше ответить на @Antti Haapala.

Ответ 3

Рабочий пример polyglot (работает одинаково для Python 2 и Python 3), используя pty.

import subprocess
import pty
import os
import sys

master, slave = pty.openpty()
# direct stderr also to the pty!
process = subprocess.Popen(
    ['ls', '-al', '--color=auto'],
    stdout=slave,
    stderr=subprocess.STDOUT
)

# close the slave descriptor! otherwise we will
# hang forever waiting for input
os.close(slave)

def reader(fd):
    try:
        while True:
            buffer = os.read(fd, 1024)
            if not buffer:
                return

            yield buffer

    # Unfortunately with a pty, an 
    # IOError will be thrown at EOF
    # On Python 2, OSError will be thrown instead.
    except (IOError, OSError) as e:
        pass

# read chunks (yields bytes)
for i in reader(master):
    # and write them to stdout file descriptor
    os.write(1, b'<chunk>' + i + b'</chunk>')

Ответ 4

Многие программы автоматически отключают коды цветной печати, когда обнаруживают, что они не подключены напрямую к терминалу. Многие программы будут иметь флаг, чтобы вы могли принудительно выводить цвет. Вы можете добавить этот флаг к вызову процесса. Например:

grep "search term" inputfile.txt 
# prints colour to the terminal in most OSes

grep "search term" inputfile.txt | less
# output goes to less rather than terminal, so colour is turned off 

grep "search term" inputfile.txt --color | less
# forces colour output even when not connected to terminal 

Будьте осторожны. Фактический цветной вывод выполняется терминалом. Терминал интерпретирует специальные коды espace символов и соответственно изменяет цвет текста и цвет фона. Без того, чтобы терминал интерпретировал цветовые коды, вы просто видите текст в черном, причем эти escape-коды перемежаются повсюду.