Python 3.4.3 subprocess.Popen 无需管道即可获取命令输出?

Python 3.4.3 subprocess.Popen get output of command without piping?

我正在尝试将命令的输出分配给变量,而不是命令认为它正在通过管道传输。这样做的原因是,如果正在通过管道传输,相关命令会提供未格式化的文本作为输出,但如果它是来自终端的 运行,则会提供彩色格式化文本。我需要获取这种颜色格式的文本。

到目前为止,我已经尝试了一些方法。我试过 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)

当命令通过管道传输时,我再次得到与命令 returns 相同的未格式化输出。

有没有办法获得格式化的输出,即命令通常在未通过管道传输时从终端给出的输出?

许多程序在检测到它们没有直接连接到终端时会自动关闭彩色打印代码。许多程序都有一个标志,因此您可以强制输出颜色。您可以将此标志添加到流程调用中。例如:

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 

不过请注意。实际颜色输出由终端完成。终端解释特殊字符空格代码并相应地更改文本颜色和背景颜色。如果没有终端来解释颜色代码,您只会看到黑色的文本,这些转义码散布在各处。

是的,你可以使用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

文档注释:

Because pseudo-terminal handling is highly platform dependent, there is code to do it only for Linux. (The Linux code is supposed to work on other platforms, but hasn’t been tested yet.)

一种不太漂亮的一次性读取整个输出的方式:

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 的回答更好。

使用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

预期:

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'

一个工作的多语言示例(对 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>')