I work with a python lib that imports a C shared library that prints on stdout. I want a clean output in order to use it with pipes or to redirect in files. The prints are done outside of python, in the shared library.
At the beginning, my approach was:
# 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)
This first approach is half working:
- For some reason, it needs a "print" statement just before moving stdout, otherwise hello word is always printed. As a result it will print an empty line instead of all the fuzz the library usually outputs.
- More annoying, it fails when redirecting to a file:
$python test.py > foo && cat foo hello world
My second python attempt was inspired from another similar thread given in the comments:
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)
This one also fails to prevent "hello" from printing.
Since I felt this was a bit low level, I then decided to go completely with ctypes. I took inspiration from this C program, which does not print anything:
#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; }
I built the following example:
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)
This prints "hello", even if I libc.fflush(stdout) just after the printf. I am starting to think it may be not possible to do what I want in python. Or maybe the way I get a file pointer to stdout is not right.
What do you think?
If you don't want that one function to print, call blockPrint() before it, and enablePrint() when you want it to continue. If you want to disable all printing, start blocking at the top of the file.
Class StdOut. Overview. The StdOut class provides methods for printing strings and numbers to standard output.
Based on @Yinon Ehrlich's answer. This variant tries to avoid leaking file descriptors:
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With