如何防止C共享库在python中的stdout上打印?

use*_*678 41 python ctypes python-2.x

我使用python lib导入一个在stdout上打印的C共享库.我想要一个干净的输出,以便与管道一起使用或重定向文件.打印是在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)
Run Code Online (Sandbox Code Playgroud)

第一种方法是半工作:
- 由于某种原因,它在移动stdout之前需要一个"print"语句,否则总是打印hello word.因此,它将打印一个空行而不是库通常输出的所有模糊.
- 更令人讨厌,重定向到文件时失败:

$python test.py > foo && cat foo

hello world
Run Code Online (Sandbox Code Playgroud)

我的第二次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)
Run Code Online (Sandbox Code Playgroud)

这个也无法阻止打印"你好".

因为我觉得这个级别有点低,所以我决定完全使用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;
}
Run Code Online (Sandbox Code Playgroud)

我构建了以下示例:

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)
Run Code Online (Sandbox Code Playgroud)

这打印"hello",即使我在printf之后的libc.fflush(stdout).我开始认为可能无法在python中做我想做的事情.或者我获取stdout文件指针的方式可能不对.

你怎么看?

jfs*_*jfs 22

根据@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
Run Code Online (Sandbox Code Playgroud)


Die*_*Epp 15

是的,你真的想用os.dup2而不是os.dup像你的第二个想法一样.你的代码看起来有点迂回.不要用/dev条目捣乱除外/dev/null,这是不必要的.在这里用C语言写任何东西也是不必要的.

诀窍是保存stdoutfdes使用dup,然后传递给fdopen新的sys.stdoutPython对象.同时,打开fdes /dev/null并使用dup2覆盖现有的stdoutfdes.然后关闭旧的fdes /dev/null.调用dup2是必要的,因为我们无法分辨open我们希望它返回哪个fdes,dup2这实际上是唯一的方法.

编辑:如果你要重定向到一个文件,那么stdout不是行缓冲的,所以你必须刷新它.你可以从Python那里做到这一点,它将正确地与C互操作.当然,如果你在写任何东西之前调用这个函数stdout,那就没关系了.

这是我刚刚测试过的可以在我的系统上运行的示例.

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..."
Run Code Online (Sandbox Code Playgroud)

"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);
}
Run Code Online (Sandbox Code Playgroud)

而输出?

$ python2.5 test.py
myfunc called
Redirecting stdout
But python can still print to stdout...
Run Code Online (Sandbox Code Playgroud)

并重定向到文件?

$ python2.5 test.py > test.txt
$ cat test.txt
myfunc called
Redirecting stdout
But python can still print to stdout...
Run Code Online (Sandbox Code Playgroud)

  • 感谢@YinonEhrlich 建议使用 `os.devnull` 而不是 `'/dev/null'`。该编辑被审稿人拒绝,我不同意该拒绝。 (2认同)

Yin*_*ich 12

将两个答案 - /sf/answers/357241881//sf/answers/292507071/结合到上下文管理器中,仅针对其范围阻止打印到stdout(第一个答案中的代码)阻止了任何外部输出,后一个答案错过了最后的sys.stdout.flush()):

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)
Run Code Online (Sandbox Code Playgroud)

  • 每次使用它时都会泄漏文件描述符.用`os.dup()`或`os.open()`创建的任何描述符都必须以某种方式关闭,这不会发生在`_old_stdout_fileno`中,如果你不在上下文中使用它,那么`_devnull`也会泄漏.泄漏文件描述符非常严重,因为你只能得到256或1024个. (4认同)