我正在使用通过ctypes模块调用的共享库。我想将与此模块关联的stdout重定向到我可以在程序中访问的变量或文件。但是,ctypes使用sys.stdout中的单独stdout。
我将用libc演示我遇到的问题。如果有人复制并粘贴代码,他们可能需要更改第2行的文件名。
import ctypes
libc = ctypes.CDLL('libc.so.6')
from cStringIO import StringIO
import sys
oldStdOut = sys.stdout
sys.stdout = myStdOut = StringIO()
print 'This text gets captured by myStdOut'
libc.printf('This text fails to be captured by myStdOut\n')
sys.stdout = oldStdOut
myStdOut.getvalue()
有没有什么方法可以捕获与ctypes加载的共享库相关联的stdout?
答案 0 :(得分:5)
我们可以使用os.dup2()
和os.pipe()
用我们可以自己读取的管道替换整个stdout文件描述符(fd 1)。你可以用stderr(fd 2)做同样的事情。
此示例使用select.select()
查看管道(我们的假stdout)是否有等待写入的数据,因此我们可以安全地打印它而不会阻止执行我们的脚本。
由于我们正在完全替换此进程和任何子进程的stdout文件描述符,因此该示例甚至可以捕获子进程的输出。
import os, sys, select
# the pipe would fail for some reason if I didn't write to stdout at some point
# so I write a space, then backspace (will show as empty in a normal terminal)
sys.stdout.write(' \b')
pipe_out, pipe_in = os.pipe()
# save a copy of stdout
stdout = os.dup(1)
# replace stdout with our write pipe
os.dup2(pipe_in, 1)
# check if we have more to read from the pipe
def more_data():
r, _, _ = select.select([pipe_out], [], [], 0)
return bool(r)
# read the whole pipe
def read_pipe():
out = ''
while more_data():
out += os.read(pipe_out, 1024)
return out
# testing print methods
import ctypes
libc = ctypes.CDLL('libc.so.6')
print 'This text gets captured by myStdOut'
libc.printf('This text fails to be captured by myStdOut\n')
# put stdout back in place
os.dup2(stdout, 1)
print 'Contents of our stdout pipe:'
print read_pipe()
答案 1 :(得分:2)
最简单的例子,因为google top中的这个问题。
import os
from ctypes import CDLL
libc = CDLL(None)
stdout = os.dup(1)
silent = os.open(os.devnull, os.O_WRONLY)
os.dup2(silent, 1)
libc.printf(b"Hate this text")
os.dup2(stdout, 1)
答案 2 :(得分:0)
如果本机进程写入的数据很大(大于管道缓冲区),本机程序将阻塞,直到您通过读取在管道中腾出一些空间。
然而,来自 lunixbochs 的解决方案需要本机进程在开始读取管道之前完成。我改进了解决方案,使其从单独的线程并行读取管道。这样您就可以捕获任何大小的输出。
这个解决方案也受到 https://stackoverflow.com/a/16571630/1076564 的启发,同时捕获 stdout 和 stderr:
class CtypesStdoutCapture(object):
def __enter__(self):
self._pipe_out, self._pipe_in = os.pipe()
self._err_pipe_out, self._err_pipe_in = os.pipe()
self._stdout = os.dup(1)
self._stderr = os.dup(2)
self.text = ""
self.err = ""
# replace stdout with our write pipe
os.dup2(self._pipe_in, 1)
os.dup2(self._err_pipe_in, 2)
self._stop = False
self._read_thread = threading.Thread(target=self._read, args=["text", self._pipe_out])
self._read_err_thread = threading.Thread(target=self._read, args=["err", self._err_pipe_out])
self._read_thread.start()
self._read_err_thread.start()
return self
def __exit__(self, *args):
self._stop = True
self._read_thread.join()
self._read_err_thread.join()
# put stdout back in place
os.dup2(self._stdout, 1)
os.dup2(self._stderr, 2)
self.text += self.read_pipe(self._pipe_out)
self.err += self.read_pipe(self._err_pipe_out)
# check if we have more to read from the pipe
def more_data(self, pipe):
r, _, _ = select.select([pipe], [], [], 0)
return bool(r)
# read the whole pipe
def read_pipe(self, pipe):
out = ''
while self.more_data(pipe):
out += os.read(pipe, 1024)
return out
def _read(self, type, pipe):
while not self._stop:
setattr(self, type, getattr(self, type) + self.read_pipe(pipe))
sleep(0.001)
def __str__(self):
return self.text
# Usage:
with CtypesStdoutCapture as capture:
lib.native_fn()
print(capture.text)
print(capture.err)