【问题标题】:Python 3.4.3 subprocess.Popen get output of command without piping?Python 3.4.3 subprocess.Popen 在没有管道的情况下获取命令输出?
【发布时间】:2015-05-09 14:24:09
【问题描述】:

我正在尝试将命令的输出分配给变量,而命令不会认为它正在通过管道传输。这样做的原因是,有问题的命令在通过管道传输时会给出未格式化的文本作为输出,但如果是从终端运行,它会给出彩色格式的文本。我需要得到这个颜色格式的文本。

到目前为止,我已经尝试了一些事情。我已经像这样尝试过 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)

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

有没有办法获得格式化的输出,当它没有被管道传输时,命令通常会从终端给出输出?

【问题讨论】:

  • 我认为“着色”是 shell/tty 的一个功能。颜色不是命令输出的一部分。因此,除了这个未格式化的输出之外,您将无法检索任何其他内容。
  • 嗨 EddyG,有时确实如此,但您可以自己将颜色格式代码发送到终端。在这种情况下就是这种情况。我仔细检查了命令本身的 C 源代码,它确实发送了颜色格式代码。所以它不仅仅是 tty。

标签: python linux bash subprocess popen


【解决方案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

期待:

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'

【讨论】:

  • 点赞。据我所知,这是唯一有效的代码示例。
  • 如果您只需要将输出作为字符串,您可以使用pexpect.runu()
  • @J.F.塞巴斯蒂安,尼斯。我刚刚阅读了文档。用那个 EOF 常量可以省去一些摸索。
  • 对于基于子进程的代码,您可以将 check_output 与 Universal_newlines=True 一起使用
  • 我用这段代码成功地实现了我的目标。感谢 7stud 和其他所有人的宝贵反馈,尤其是 J. F. Sebastian 和 Antti Haapala。我现在有另一个特定于 pexpect.spawn 的问题,但我会制作另一个线程。只要我的合作者没有产生额外的 pexpect 依赖,这应该可以工作。谢谢!
【解决方案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 的答案更好。

【讨论】:

  • 您可以使用os.fdopen 将主描述符作为python 文件打开
  • 是的,pty 可能会欺骗命令,使其认为它是在终端中以交互方式运行的(尽管如果程序提供了--color 选项,例如ls;你可以通过管道输出) 1. 你应该提到pty 适用于Linux(它也可能适用于其他*nice)。如果您使用stdout=slave,则无需致电p.communicate() -- p.wait() 就足够了。您可能应该在命令仍在运行时读取master,否则程序可能会在填满相应的输出缓冲区后永远挂起,code example pexpect
  • 在读取来自master 的输出之前关闭slave 文件描述符可能会导致I/O 错误(它不应该但确实会)。我不得不put os.close(slave_fd) after the while 1 that reads the output to make the code work。此外,f.read() 可能会永远挂起 - 试试看(我不完全确定这是很久以前的事了)。
  • 嗯,很有趣。我在玩os.fdopen 建议,但如果我没有先关闭从站,read 调用就会挂起。它似乎工作正常..你知道什么会导致它失败吗?
  • 这是因为在从属保持打开状态时,主控永远不会遇到“EOF”。
【解决方案3】:

使用pty 的多语言工作示例(Python 2 和 Python 3 的工作方式相同)。

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

【讨论】:

  • 1.它不应该在 Python 3 上工作,请改用 os.write(1, b'a')。 2.Linux上如果在读取数据之前关闭slave,可能会引发异常。 3. 使用EnvironmentError 捕获IOErrorOSError(它们在Python 3 上都是一样的)。
  • 修复了1;至于2,异常处理是为了捕捉异常;和 3,我将它们明确地放在元组中,以便理解错误不是专门的 IOError OSError。
  • 您是否断言总是引发异常并且总是读取所有数据(假设没有其他错误)?我不明白写(IOError, OSError)而不是EnvironmentError的意义。
  • 修复了将 EOF 作为空字符串的情况(对我来说,EOF 总是导致 EIO)。
  • Python 2 在此处的 EOF 上引发 OSError。 OSError 是 Python 3 上的 IOError(字面意思)。同时使用 (IOError, OSError) 有什么意义?你可能应该reraise non-EIO errors here
【解决方案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 代码并相应地更改文本颜色和背景颜色。如果没有终端来解释颜色代码,您只会看到黑色文本,其中散布着这些转义代码。

【讨论】:

  • 感谢 Dunes,这是在正确的轨道上,但还不够。在这种情况下,此命令的应用程序有一个配置文件。用户可以将颜色设置为从不、自动或始终。所以我不能自己强制颜色,我必须尝试获得非管道命令给出的确切输出。如果我强制使用颜色,那么如果他们碰巧从未使用过,我将覆盖用户配置。
  • @nullified: 如果你想模拟script 命令,你可以试试pty.spawn() or pexpect
猜你喜欢
  • 1970-01-01
  • 2015-08-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-27
  • 2015-03-25
  • 1970-01-01
  • 2010-09-18
相关资源
最近更新 更多