46

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?

user48678
  • 1,833
  • 2
  • 20
  • 29
  • 3
    Where do you get the segfault (stack trace)? And, sincerely... shame on the developper of the shared lib. Writing directly to stdout from within a shared library without providing a means to change this behaviour is baaad. – Axel Feb 22 '11 at 17:39
  • Unfortunately, I can't find any way of redirecting stdout from _within_ Python. I think you're on the right track here with wrapping your shared library in C, making a dll out of the wrapper and using ctypes to call that. I believe your segfault is due to the `sprintf`, but I can't really tell what the problem is. – Chinmay Kanchi Feb 22 '11 at 18:22
  • 3
    possible duplicate of [Suppressing output of module calling outside library](http://stackoverflow.com/questions/4178614/suppressing-output-of-module-calling-outside-library) – Ignacio Vazquez-Abrams Feb 22 '11 at 18:33
  • I don't get a segfault anymore (and I no more use sprintf), sorry for making your comments outdated but I felt the post is long enough without a stack trace. – user48678 Feb 23 '11 at 08:08
  • related: [Redirect stdout to a file in Python?](http://stackoverflow.com/a/22434262/4279) – jfs May 06 '14 at 22:50

5 Answers5

30

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
Community
  • 1
  • 1
jfs
  • 346,887
  • 152
  • 868
  • 1,518
  • Marked as best answer because this one does not leak. However, I believe that using a decorator, like @Yinon Ehrlich would be more elegant. – user48678 Oct 28 '13 at 20:02
  • @user48678: I don't see any decorators in [@Yinon Ehrlich's answer](http://stackoverflow.com/a/14797594/4279). `HideOutput` is a context manager defined using a class; `stdout_redirected` is also a context manager but it is defined using [`contextlib.contextmanager` decorator](http://docs.python.org/2/library/contextlib.html#contextlib.contextmanager) (I could have used a class here with the same result) – jfs Oct 28 '13 at 22:34
  • Sebastian: Well HideOutput _is_ a decorator. The usage is 'with HideOutput:'. But I did not know contextlib.contextmanager. From the documentation, it is actually a decorator too. I had missed that. – user48678 Oct 30 '13 at 11:56
  • 1
    @user48678: You've mixed the concepts. `with`-statement uses context managers, examples: files, locks, `decimal.localcontext()` (all are objects with `__enter__`, `__exit__` methods). They are different from decorators that are callables (e.g., functions) that accept a callable and return a callable (as a rule), examples: `staticmethod`, `classmethod`, [`makebold`,`makeitalic`](http://stackoverflow.com/q/739654/4279). Python supports special `@` syntax for decorators. – jfs Oct 31 '13 at 09:01
  • 1
    Absolutely, you are right. I just checked my facts, and a decorator is a completely different thing from what I meant. Apologies for the mistake. – user48678 Nov 01 '13 at 23:45
  • @J.F. Sebastian: thanks for clarifying the files descriptors leak and for introducing me with the `contextlib.contextmanager` decorator – Yinon Ehrlich Nov 04 '13 at 15:56
  • Can anyone explain me what is the problem with the code above, that gave me the following: `Traceback (most recent call last): File "...", line 474, in open with stdout_redirected(): File "/usr/lib/python2.7/contextlib.py", line 17, in __enter__ return self.gen.next() File "...", line 191, in stdout_redirected with os.fdopen(os.dup(fd), 'w') as old_stdout: OSError: [Errno 9] Bad file descriptor` ??? – Yinon Ehrlich Nov 10 '13 at 07:00
  • @YinonEhrlich: what is `fd`? Is it open for writing? – jfs Nov 10 '13 at 13:07
  • @YinonEhrlich: I meant, are you sure that `sys.stdout.fileno()` returns an appropriate value? For example, `idle`, `bpython` overwrite `sys.stdout`. Notice the comment: *"assert that Python and C stdio write using the same file descriptor"*. Also the function is not reentrant and it is not safe to use it from multiple threads. – jfs Nov 12 '13 at 13:53
  • Can this be modified to work with a filename or a file-like object like `StringIO` or `ByteIO` ? – Brandon Dube Nov 22 '17 at 02:07
  • @jfs - easily? I've been poking around and it seems you need to use a `tempfile.SpooledTemporaryFile` to have all the proper os-level file interfaces like a descriptor. – Brandon Dube Nov 22 '17 at 02:34
  • @BrandonDube click the link – jfs Nov 22 '17 at 02:36
  • @jfs as far as I understand (I'm not a linux user) `/dev/null` is somewhere you send data to die. I want to recover the data from `print` statements in C/Fortran linked code. I could use this with a filename and write to a .txt file, then read from it and delete it, or I could do it in memory with e.g. StringIO. It is non-obvious how to do this with this answer -- the `.fileno()` attribute of `to` is not present on a `BytesIO` or `StringIO` object. – Brandon Dube Nov 22 '17 at 02:42
  • @BrandonDube pass a different name instead of /dev/null – jfs Nov 22 '17 at 02:44
  • @jfs - that will write to a file. I would prefer to not thrash the filesystem by needlessly making a real file, only to immediately read and delete it. – Brandon Dube Nov 22 '17 at 02:49
  • @BrandonDube read your first comment. Do you see "filename" word in it? Then read your comment that says io.BytesIO() doesn't provide .fileno(). It seems you've answered your question. – jfs Nov 22 '17 at 03:01
  • @jfs -- allow me to rephrase -- when I wrote _or_, I meant _or_ as in _interoperably_, the function works with _either_ a filename input, _or_ a `BytesIO` _or_ a `StringIO` object seamlessly. – Brandon Dube Nov 22 '17 at 03:02
  • @jfs Great work, however it look like the descriptor does still leak in a multithreading app. Do you think it is fixable? – Solon Jul 11 '20 at 06:54
  • @Solon: the function manipulates global data. If you need to use multiple threads, then the access to the data should be serialized e.g., use a mutex around the code – jfs Jul 11 '20 at 14:36
  • I obtained the following error in Python 3.7: `AttributeError: 'DupStdoutFileWriter' object has no attribute 'fileno'`. Would you have a suggestion for a fix? Thanks. – Khue Feb 22 '21 at 18:40
17

Yeah, you really want to use os.dup2 instead of os.dup, like your second idea. Your code looks somewhat roundabout. Don't muck about with /dev entries except for /dev/null, it's unnecessary. It's also unnecessary to write anything in C here.

The trick is to save the stdout fdes using dup, then pass it to fdopen to make the new sys.stdout Python object. Meanwhile, open an fdes to /dev/null and use dup2 to overwrite the existing stdout fdes. Then close the old fdes to /dev/null. The call to dup2 is necessary because we can't tell open which fdes we want it to return, dup2 is really the only way to do that.

Edit: And if you're redirecting to a file, then stdout is not line-buffered, so you have to flush it. You can do that from Python and it will interoperate with C correctly. Of course, if you call this function before you ever write anything to stdout, then it doesn't matter.

Here is an example that I just tested that works on my system.

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..."

The "zook" module is a very simple library in 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);
}

And the output?

$ python2.5 test.py
myfunc called
Redirecting stdout
But python can still print to stdout...

And redirecting to files?

$ python2.5 test.py > test.txt
$ cat test.txt
myfunc called
Redirecting stdout
But python can still print to stdout...
Dietrich Epp
  • 182,361
  • 34
  • 307
  • 387
  • 1
    Thanks @YinonEhrlich for the suggestion to use `os.devnull` instead of `'/dev/null'`. The edit was rejected by reviewers and I disagree with the rejection. – Dietrich Epp Feb 10 '13 at 10:56
  • can this approach be used to redirect to a file created from the logger module? – BigBrownBear00 Sep 14 '20 at 18:39
  • In theory, yes, but how would you know which file descriptor to replace? How would you know which object to flush? Think about it like using dynamite to open a door because you can't be bothered to find the key. – Dietrich Epp Sep 14 '20 at 19:47
  • That's the crux of the question .. if and how can it be done with a Logger object? – BigBrownBear00 Sep 14 '20 at 19:53
  • I don't even know what a "Logger object" is, and there is probably a larger discussion to be had here (like, what are you trying to do? rotate logs? redirect logs?) that I would lean towards asking this as its own question. – Dietrich Epp Sep 14 '20 at 20:20
14

Combining both answers - https://stackoverflow.com/a/5103455/1820106 & https://stackoverflow.com/a/4178672/1820106 to context manager that blocks print to stdout only for its scope (the code in the first answer blocked any external output, the latter answer missed the sys.stdout.flush() at end):

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)
Community
  • 1
  • 1
Yinon Ehrlich
  • 476
  • 4
  • 14
  • This very neat. The [entry](http://stackoverflow.com/questions/5081657/how-do-i-prevent-a-c-shared-library-to-print-on-stdout-in-python/5103455#5103455) from [Dietrich Epp](http://stackoverflow.com/users/82294/dietrich-epp) was the one that solved my issue at the time I asked this question and he still gets my vote, but yours is more elegant. I am marking it as best. – user48678 Apr 17 '13 at 18:07
  • 5
    This will leak file descriptors every time you use it. Any descriptor created with `os.dup()` or `os.open()` must be somehow closed, and this doesn't happen to `_old_stdout_fileno`, and if you don't use it in a context then `_devnull` will also leak. Leaking file descriptors is pretty serious because you only get something like 256 or 1024 of them. – Dietrich Epp Apr 17 '13 at 22:21
  • 1
    @DietrichEpp: I've posted [solution that tries to avoid leaking file descriptors](http://stackoverflow.com/a/17954769/4279). – jfs Jul 30 '13 at 18:37
4

Here is how I finally did. I hope this can be useful for other people (this works on my linux station).

I proudly present the libshutup, designed for making external libraries shut up.

1) Copy the following file

// 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) Compile it as a shared library

gcc -Wall -shared shutup.c -fPIC -o libshutup.so

3) Use it in you code like this

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()
user48678
  • 1,833
  • 2
  • 20
  • 29
  • All that to replace 5 lines of Python. – Ignacio Vazquez-Abrams Feb 24 '11 at 10:23
  • If you have a better answer, I'd be delighted to have it. You can run every examples above and see they all print something. For now, this is the only solution that **really** works for me. – user48678 Feb 24 '11 at 10:28
  • This problem is solved in the question that this one is a duplicate of. – Ignacio Vazquez-Abrams Feb 24 '11 at 10:39
  • 2
    @Ignacio No it's not. Just try to run the piece of code given in my second attempt, which is exactly your advice in the post, and you'll see that. – user48678 Feb 24 '11 at 12:18
  • Nice solution, It works for my specific case. None of the solutions that I found before didn't completely work. One thing I want to do is that to return stdout as a char array so to control in Python instead of writing into "/dev/fd/%d". Is there a way to do that? I'm very new to both C & ctypes. Sorry for my ignorance if this is too obvious question. – 윤제균 Jan 17 '20 at 07:56
  • Have you tried the answer I accepted? Because yes, creating my own C library was working for me, but in the end the accepted answer enabled me to achieve the same in pure python, so I consider it a better approach. By the way this question is 8 years old, it's funny how it is still relevant today :-) – user48678 Jan 18 '20 at 17:49
  • @user48678 Thanks for the reply. I just tried the accepted answer and it works well. But I would like to capture the stdout into a variable instead of just redirecting, so I can use later on. Is it possible to get the contents of the redirected std_out? – 윤제균 Jan 20 '20 at 07:33
-2

Wouldn't you be able to do this the same as you would in Python? You'd import sys and point sys.stdout and sys.stderr to something that isn't the default sys.stdout and sys.stderr? I do this all the time in a few apps where I have to slurp up output from a library.

Jeremy Whitlock
  • 3,700
  • 24
  • 16
  • No, because `sys.std*` are used only by Python code, whereas C libraries use FDs 0 through 2 directly. – Ignacio Vazquez-Abrams Feb 23 '11 at 01:15
  • 1
    This should be a comment, not an answer. – Will Sep 02 '13 at 09:08
  • Actually, it was an answer but without working code, I could see how it didn't get the message across. Back in 2011, I didn't realize the amount of hand holding you had to do on StackOverflow. Regardless, not sure what you get out of picking nits and giving it a downvote...2.5 years later. – Jeremy Whitlock Sep 05 '13 at 06:22