Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I prevent a C shared library to print on stdout in python?

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?

like image 828
user48678 Avatar asked Feb 22 '11 17:02

user48678


People also ask

How do you stop a print function in Python?

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.

Does print go to stdout?

Class StdOut. Overview. The StdOut class provides methods for printing strings and numbers to standard output.


1 Answers

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 
like image 165
jfs Avatar answered Sep 17 '22 14:09

jfs