PEP 324 – subprocess - New process module | peps.python.org (original) (raw)

Author:

Peter Astrand

Status:

Final

Type:

Standards Track

Created:

19-Nov-2003

Python-Version:

2.4

Post-History:


Table of Contents

Abstract

This PEP describes a new module for starting and communicating with processes.

Motivation

Starting new processes is a common task in any programming language, and very common in a high-level language like Python. Good support for this task is needed, because:

Currently, Python has a large number of different functions for process creation. This makes it hard for developers to choose.

The subprocess module provides the following enhancements over previous functions:

Rationale

The following points summarizes the design:

Specification

This module defines one class called Popen:

class Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0):

Arguments are:

This module also defines two shortcut functions:

Exceptions

Exceptions raised in the child process, before the new program has started to execute, will be re-raised in the parent. Additionally, the exception object will have one extra attribute called ‘child_traceback’, which is a string containing traceback information from the child’s point of view.

The most common exception raised is OSError. This occurs, for example, when trying to execute a non-existent file. Applications should prepare for OSErrors.

A ValueError will be raised if Popen is called with invalid arguments.

Security

Unlike some other popen functions, this implementation will never call /bin/sh implicitly. This means that all characters, including shell meta-characters, can safely be passed to child processes.

Popen objects

Instances of the Popen class have the following methods:

poll()

Check if child process has terminated. Returns returncodeattribute.

wait()

Wait for child process to terminate. Returns returncodeattribute.

communicate(input=None)

Interact with process: Send data to stdin. Read data from stdout and stderr, until end-of-file is reached. Wait for process to terminate. The optional stdin argument should be a string to be sent to the child process, or None, if no data should be sent to the child.

communicate() returns a tuple (stdout, stderr).

Note: The data read is buffered in memory, so do not use this method if the data size is large or unlimited.

The following attributes are also available:

stdin

If the stdin argument is PIPE, this attribute is a file object that provides input to the child process. Otherwise, it isNone.

stdout

If the stdout argument is PIPE, this attribute is a file object that provides output from the child process. Otherwise, it is None.

stderr

If the stderr argument is PIPE, this attribute is file object that provides error output from the child process. Otherwise, it is None.

pid

The process ID of the child process.

returncode

The child return code. A None value indicates that the process hasn’t terminated yet. A negative value -N indicates that the child was terminated by signal N (UNIX only).

Replacing older functions with the subprocess module

In this section, “a ==> b” means that b can be used as a replacement for a.

Note: All functions in this section fail (more or less) silently if the executed program cannot be found; this module raises an OSError exception.

In the following examples, we assume that the subprocess module is imported with from subprocess import *.

Replacing /bin/sh shell backquote

output=mycmd myarg ==> output = Popen(["mycmd", "myarg"], stdout=PIPE).communicate()[0]

Replacing shell pipe line

output=dmesg | grep hda ==> p1 = Popen(["dmesg"], stdout=PIPE) p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE) output = p2.communicate()[0]

Replacing os.system()

sts = os.system("mycmd" + " myarg") ==> p = Popen("mycmd" + " myarg", shell=True) sts = os.waitpid(p.pid, 0)

Note:

A more real-world example would look like this:

try: retcode = call("mycmd" + " myarg", shell=True) if retcode < 0: print >>sys.stderr, "Child was terminated by signal", -retcode else: print >>sys.stderr, "Child returned", retcode except OSError, e: print >>sys.stderr, "Execution failed:", e

Replacing os.spawn*

P_NOWAIT example:

pid = os.spawnlp(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg") ==> pid = Popen(["/bin/mycmd", "myarg"]).pid

P_WAIT example:

retcode = os.spawnlp(os.P_WAIT, "/bin/mycmd", "mycmd", "myarg") ==> retcode = call(["/bin/mycmd", "myarg"])

Vector example:

os.spawnvp(os.P_NOWAIT, path, args) ==> Popen([path] + args[1:])

Environment example:

os.spawnlpe(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg", env) ==> Popen(["/bin/mycmd", "myarg"], env={"PATH": "/usr/bin"})

Replacing os.popen*

pipe = os.popen(cmd, mode='r', bufsize) ==> pipe = Popen(cmd, shell=True, bufsize=bufsize, stdout=PIPE).stdout

pipe = os.popen(cmd, mode='w', bufsize) ==> pipe = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE).stdin

(child_stdin, child_stdout) = os.popen2(cmd, mode, bufsize) ==> p = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE, stdout=PIPE, close_fds=True) (child_stdin, child_stdout) = (p.stdin, p.stdout)

(child_stdin, child_stdout, child_stderr) = os.popen3(cmd, mode, bufsize) ==> p = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) (child_stdin, child_stdout, child_stderr) = (p.stdin, p.stdout, p.stderr)

(child_stdin, child_stdout_and_stderr) = os.popen4(cmd, mode, bufsize) ==> p = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True) (child_stdin, child_stdout_and_stderr) = (p.stdin, p.stdout)

Replacing popen2.*

Note: If the cmd argument to popen2 functions is a string, the command is executed through /bin/sh. If it is a list, the command is directly executed.

(child_stdout, child_stdin) = popen2.popen2("somestring", bufsize, mode) ==> p = Popen(["somestring"], shell=True, bufsize=bufsize stdin=PIPE, stdout=PIPE, close_fds=True) (child_stdout, child_stdin) = (p.stdout, p.stdin)

(child_stdout, child_stdin) = popen2.popen2(["mycmd", "myarg"], bufsize, mode) ==> p = Popen(["mycmd", "myarg"], bufsize=bufsize, stdin=PIPE, stdout=PIPE, close_fds=True) (child_stdout, child_stdin) = (p.stdout, p.stdin)

The popen2.Popen3 and popen3.Popen4 basically works assubprocess.Popen, except that:

Open Issues

Some features have been requested but is not yet implemented. This includes:

While these are useful features, it’s expected that these can be added later without problems.

pty support is highly platform-dependent, which is a problem. Also, there are already other modules that provide this kind of functionality [6].

Backwards Compatibility

Since this is a new module, no major backward compatible issues are expected. The module name “subprocess” might collide with other, previous modules [3] with the same name, but the name “subprocess” seems to be the best suggested name so far. The first name of this module was “popen5”, but this name was considered too unintuitive. For a while, the module was called “process”, but this name is already used by Trent Mick’s module [4].

The functions and modules that this new module is trying to replace (os.system, os.spawn*, os.popen*, popen2.*,commands.*) are expected to be available in future Python versions for a long time, to preserve backwards compatibility.

Reference Implementation

A reference implementation is available fromhttp://www.lysator.liu.se/~astrand/popen5/.

References

This document has been placed in the public domain.