Как предотвратить публикацию совместно используемой библиотеки C на stdout в python?
Я работаю с библиотекой python, которая импортирует совместно используемую библиотеку C, которая печатает на stdout. Я хочу чистый вывод, чтобы использовать его с каналами или перенаправлять файлы. Отпечатки выполняются за пределами python в общей библиотеке.
В начале мой подход:
# file: test.py
import os
from ctypes import *
from tempfile import mktemp
libc = CDLL("libc.so.6")
print # That 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 pretend this is a call to my library
libc.printf("hello world\n")
os.close(1)
os.dup(savestdout)
os.close(savestdout)
Этот первый подход работает наполовину:
- По какой-то причине ему нужно выполнить "печать" непосредственно перед перемещением stdout, иначе приветственное слово всегда печатается. В результате он будет печатать пустую строку, а не весь fuzz, который обычно выводит библиотека.
- Больше раздражает, он не работает при перенаправлении в файл:
$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;
}
Я построил следующий пример:
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)
Это печатает "привет", даже если я libc.fflush(stdout) сразу после printf. Я начинаю думать, что может быть невозможно сделать то, что я хочу в python. Или, может быть, способ, которым я получаю указатель на файл stdout, не прав.
Как вы думаете?
Ответы
Ответ 1
На основе @Yinon Ehrlich answer. Этот вариант пытается избежать утечки дескрипторов файлов:
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
Ответ 2
Да, вы действительно хотите использовать os.dup2
вместо os.dup
, как и ваша вторая идея. Ваш код выглядит несколько круто. Не отчаивайтесь с /dev
записями, за исключением /dev/null
, это не нужно. Здесь также нет необходимости писать что-либо в C.
Фокус в том, чтобы сохранить fdes stdout
, используя dup
, затем передать его в fdopen
, чтобы создать новый объект sys.stdout
Python. Между тем, откройте fdes до /dev/null
и используйте dup2
, чтобы перезаписать существующие stdout
fdes. Затем закройте старые 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..."
Модуль "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...
И перенаправление на файлы?
$ python2.5 test.py > test.txt
$ cat test.txt
myfunc called
Redirecting stdout
But python can still print to stdout...
Ответ 3
Объединение обоих ответов - fooobar.com/questions/146124/... и fooobar.com/questions/146130/... в контекстный менеджер, который блокирует печать на 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)
Ответ 4
Вот как я наконец это сделал. Я надеюсь, что это может быть полезно для других людей (это работает на моей Linux-станции).
Я с гордостью представляю 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 pretend this printf comes from the external lib
libc = CDLL("libc.so.6")
libc.printf("hello\n")
shutup.stdout_on()
Ответ 5
Разве вы не могли бы сделать это так же, как и в Python? Вы импортируете sys и point sys.stdout и sys.stderr в то, что не является стандартным sys.stdout и sys.stderr? Я делаю это все время в нескольких приложениях, где мне приходится выполнять вывод из библиотеки.