Archive

Posts Tagged ‘stdout’

Capture the exit code, the stdout, and the stderr of an external command

January 8, 2014 Leave a comment

Update (20140110): The get_exitcode_stdout_stderr function was improved. Thanks to @Rhomboid for the tip. You will find the old version at the end of the post.


Problem
You have an arbitrary program that you want to execute as an external command, i.e. you have a wrapper around it. In the wrapper script you want to get the exit code, the stdout, and the stderr of the executed program.

Solution
Let the external command be this simple C program saved as test.c:

#include <stdio.h>

int main()
{
    printf("go 2 stdout\n");
    fprintf(stderr, "go 2 stderr\n");
    return 3;
}

Compile and run it:

$ gcc test.c
$ ./a.out
go 2 stdout
go 2 stderr

Note that this external command can be anything, written in any language.

After executing it, we pose the following questions:

  1. What was its exit code?
  2. What did it send to the stdout?
  3. What did it send to the stderr?

Here is a wrapper that can capture all these three things. Thanks to @Rhomboid for his tip on improving the get_exitcode_stdout_stderr function.

#!/usr/bin/env python
# encoding: utf-8

from __future__ import (absolute_import, division,
                        print_function, unicode_literals)

import sys
import shlex
from subprocess import Popen, PIPE


def frame(text):
    """
    Put the text in a pretty frame.
    """
    result = """
+{h}+
|{t}|
+{h}+
""".format(h='-' * len(text), t=text).strip()
    return result


def get_exitcode_stdout_stderr(cmd):
    """
    Execute the external command and get its exitcode, stdout and stderr.
    """
    args = shlex.split(cmd)

    proc = Popen(args, stdout=PIPE, stderr=PIPE)
    out, err = proc.communicate()
    exitcode = proc.returncode
    #
    return exitcode, out, err


def main(params):
    cmd = ' '.join(params)
    exitcode, out, err = get_exitcode_stdout_stderr(cmd)

    print(frame("EXIT CODE"))
    print(exitcode)

    print(frame("STDOUT"))
    print(out)

    print(frame("STDERR"))
    print(err)

####################

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print('Usage: {} <external_command>'.format(sys.argv[0]))
        sys.exit(1)
    # else
    main(sys.argv[1:])

Running example

$ ./wrapper.py ./a.out
+---------+
|EXIT CODE|
+---------+
3
+------+
|STDOUT|
+------+
go 2 stdout

+------+
|STDERR|
+------+
go 2 stderr

The script wrapper.py can be called in various ways:

$ ./wrapper.py python print.py    # pass the command in several pieces
...
$ ./wrapper.py "python print.py"    # you can pass the command as one argument
...

Fine, but what is it good for?
I’m working on an online judge whose job is to execute a program with different parameters and decide whether it’s correct or not. I want to execute the programs in a sandboxed (secure) environment and I need to get the output (and the errors) of the programs to analyze how they ran. The wrapper script above is a first step in this direction.


Update (20140110)
First I solved this problem by writing the stdout and stderr to temporary files. As it turned out, temp. files are not necessary, so the post above was updated accordingly. Here I leave my old solution:

import shlex
import tempfile
from subprocess import call

# Warning! The code above is better! This is the old version!

def get_exitcode_stdout_stderr(cmd):
    """
    Execute the external command and get its exitcode, stdout and stderr.
    """
    args = shlex.split(cmd)

    try:
        fout = tempfile.TemporaryFile()
        ferr = tempfile.TemporaryFile()
        exitcode = call(args, stdout=fout, stderr=ferr)
        fout.seek(0)
        ferr.seek(0)
        out, err = fout.read(), ferr.read()
    finally:
        fout.close()
        ferr.close()
    #
    return exitcode, out, err

<old>
You might be tempted to redirect the stdout and stderr in the function call(...) to a string (to a StringIO) instead of a file. Unfortunately it doesn’t work. Although a StringIO behaves like a file-like object, it’s not a file, thus it doesn’t have a fileno() method and you would get an error because of this (see this thread for instance).

So, we must redirect the outputs to files. After reading the content of these files, they can be removed, thus we use tempfiles from the standard library. When a tempfile.TemporaryFile is closed, it is removed, so we don’t need to unlink them (more info @pymotw and @docs).
</old>

Redirect stdout to a file

August 17, 2011 1 comment

Problem
Your script produces some output to the standard output and you want to redirect it to a file. For instance, you want to suppress the output to /dev/null. However, you want to do this redirection from your script and not in the shell.

Solution
Add the following lines to the beginning of your script (before the first print call):

old_stdout = sys.stdout
sys.stdout = open(os.devnull, 'w')

Of course, instead of os.devnull you can specify a normal text file too. To restore printing to stdout, point sys.stdout to old_stdout.

Related
In this thread you will see a solution for redirecting the stdout to a string.

Print in Python 2

October 1, 2010 Leave a comment

Let’s see some examples how to print to the standard output:

#!/usr/bin/env python

import sys

a = "Alice"
b = "Bob"

print a     # "Alice\n", i.e. newline added
print b     # "Bob\n"

print a, b  # "Alice Bob\n", i.e. space is used as separator

print a + b # "AliceBob\n", i.e. the two strings are concatenated

for i in range(10):
    print i,    # "0 1 2 3 4 5 6 7 8 9", i.e. no newline, but space is still added
                # notice the comma after i

print       # "\n"

age = 7.5
print "Alice is " + str(age) + " years old."    # must use str() for converting
                                                # the float to string

print "%s is %.1f years old" % (a, age)         # like C's printf()
    
for i in range(10):
    sys.stdout.write(str(i))    # "0123456789", now you have full control
  
print

Using the pprint module, you can “pretty print” any data structure. It’s similar to PHP’s print_r function.

import pprint
pp = pprint.PrettyPrinter(indent=4)

li = []   # some complicated structure

pp.pprint(li)

How to print to the standard error? Easy:

#!/usr/bin/env python

import sys

sys.stderr.write("Reactor meltdown! Leave the building immediately!\n")
Categories: python Tags: , , , ,