如何防止C共享库打印在Python的标准输出?
我使用一个python库导入一个打印在标准输出上的C共享库。 我想要一个干净的输出,以便使用pipe道或redirect文件。 打印是在Python之外的共享库中完成的。
一开始,我的做法是:
# file: test.py import os from ctypes import * from tempfile import mktemp libc = CDLL("libc.so.6") print # That's here on purpose, otherwise hello word is always printed tempfile = open(mktemp(),'w') savestdout = os.dup(1) os.close(1) if os.dup(tempfile.fileno()) != 1: assert False, "couldn't redirect stdout - dup() error" # let's pretend this is a call to my library libc.printf("hello world\n") os.close(1) os.dup(savestdout) os.close(savestdout)
这第一种方法是一半工作:
– 出于某种原因,在移动stdout之前它需要一个“打印”语句,否则总是打印出你好的单词。 因此,它将打印一个空行,而不是库中通常输出的所有文件。
– 更烦人,redirect到文件时失败:
$python test.py > foo && cat foo hello world
我的第二个python尝试是从评论中给出的另一个类似的线程的启发:
import os import sys from ctypes import * libc = CDLL("libc.so.6") devnull = open('/dev/null', 'w') oldstdout = os.dup(sys.stdout.fileno()) os.dup2(devnull.fileno(), 1) # We still pretend this is a call to my library libc.printf("hello\n") os.dup2(oldstdout, 1)
这也不能防止打印“你好”。
因为我觉得这个水平有点低,所以我决定完全用ctypes。 我从这个C程序中汲取灵感,它不打印任何东西:
#include <stdio.h> int main(int argc, const char *argv[]) { char buf[20]; int saved_stdout = dup(1); freopen("/dev/null", "w", stdout); printf("hello\n"); // not printed sprintf(buf, "/dev/fd/%d", saved_stdout); freopen(buf, "w", stdout); return 0; }
我build立了以下例子:
from ctypes import * libc = CDLL("libc.so.6") saved_stdout = libc.dup(1) stdout = libc.fdopen(1, "w") libc.freopen("/dev/null", "w", stdout); libc.printf("hello\n") libc.freopen("/dev/fd/" + str(saved_stdout), "w", stdout)
这会打印“hello”,即使我在printf之后使用了libc.fflush(stdout)。 我开始认为这可能是不可能做到我想在Python中。 或者,也许我得到一个文件指针stdout的方式是不正确的。
你怎么看?
基于@Yinon Ehrlich的回答 。 这个变体试图避免泄露文件描述符:
import os import sys from contextlib import contextmanager @contextmanager def stdout_redirected(to=os.devnull): ''' import os with stdout_redirected(to=filename): print("from Python") os.system("echo non-Python applications are also supported") ''' fd = sys.stdout.fileno() ##### assert that Python and C stdio write using the same file descriptor ####assert libc.fileno(ctypes.c_void_p.in_dll(libc, "stdout")) == fd == 1 def _redirect_stdout(to): sys.stdout.close() # + implicit flush() os.dup2(to.fileno(), fd) # fd writes to 'to' file sys.stdout = os.fdopen(fd, 'w') # Python writes to fd with os.fdopen(os.dup(fd), 'w') as old_stdout: with open(to, 'w') as file: _redirect_stdout(to=file) try: yield # allow code to be run with the redirected stdout finally: _redirect_stdout(to=old_stdout) # restore stdout. # buffering and flags such as # CLOEXEC may be different
是的,你真的想使用os.dup2
而不是os.dup
,就像你的第二个想法。 你的代码看起来有些迂回。 除了/dev/null
之外,不要使用/dev
条目,这是不必要的。 这里也没有必要用C写任何东西。
诀窍是使用dup
保存stdout
fdes,然后将其传递给fdopen
以创build新的sys.stdout
Python对象。 同时,打开fdes到/dev/null
并使用dup2
覆盖现有的stdout
fdes。 然后closures旧的fdes到/dev/null
。 对dup2
的调用是必要的,因为我们不能说明我们希望它返回哪个fdes, dup2
真的是唯一的方法。
编辑:如果你redirect到一个文件,然后标准输出不是行缓冲,所以你必须刷新它。 你可以用Python来做到这一点,它将正确地与C进行互操作。 当然,如果你在向stdout
写任何东西之前调用这个函数,那么没关系。
这里是我刚testing过的一个例子,在我的系统上工作。
import zook import os import sys def redirect_stdout(): print "Redirecting stdout" sys.stdout.flush() # <--- important when redirecting to files newstdout = os.dup(1) devnull = os.open(os.devnull, os.O_WRONLY) os.dup2(devnull, 1) os.close(devnull) sys.stdout = os.fdopen(newstdout, 'w') zook.myfunc() redirect_stdout() zook.myfunc() print "But python can still print to stdout..."
“zook”模块是C中一个非常简单的库
#include <Python.h> #include <stdio.h> static PyObject * myfunc(PyObject *self, PyObject *args) { puts("myfunc called"); Py_INCREF(Py_None); return Py_None; } static PyMethodDef zookMethods[] = { {"myfunc", myfunc, METH_VARARGS, "Print a string."}, {NULL, NULL, 0, NULL} }; PyMODINIT_FUNC initzook(void) { (void)Py_InitModule("zook", zookMethods); }
和输出?
$ python2.5 test.py myfunc called Redirecting stdout But python can still print to stdout...
并redirect到文件?
$ python2.5 test.py > test.txt $ cat test.txt myfunc called Redirecting stdout But python can still print to stdout...
class HideOutput(object): ''' A context manager that block stdout for its scope, usage: with HideOutput(): os.system('ls -l') ''' def __init__(self, *args, **kw): sys.stdout.flush() self._origstdout = sys.stdout self._oldstdout_fno = os.dup(sys.stdout.fileno()) self._devnull = os.open(os.devnull, os.O_WRONLY) def __enter__(self): self._newstdout = os.dup(1) os.dup2(self._devnull, 1) os.close(self._devnull) sys.stdout = os.fdopen(self._newstdout, 'w') def __exit__(self, exc_type, exc_val, exc_tb): sys.stdout = self._origstdout sys.stdout.flush() os.dup2(self._oldstdout_fno, 1)
这是我终于做到的。 我希望这可以为其他人(这在我的Linux站上工作)有用。
我自豪地介绍了为使外部图书馆闭嘴而devise的libshutup。
1)复制下面的文件
// file: shutup.c #include <stdio.h> #include <unistd.h> static char buf[20]; static int saved_stdout; void stdout_off() { saved_stdout = dup(1); freopen("/dev/null", "w", stdout); } void stdout_on() { sprintf(buf, "/dev/fd/%d", saved_stdout); freopen(buf, "w", stdout); }
2)将其编译为共享库
gcc -Wall -shared shutup.c -fPIC -o libshutup.so
3)在你这样的代码中使用它
from ctypes import * shutup = CDLL("libshutup.so") shutup.stdout_off() # Let's pretend this printf comes from the external lib libc = CDLL("libc.so.6") libc.printf("hello\n") shutup.stdout_on()
难道你不能像Python中那样做吗? 你会导入sys和点sys.stdout和sys.stderr到不是默认的sys.stdout和sys.stderr? 我经常在几个应用程序中这样做,我不得不从库中获取输出。