popen2.py 6.63 KB
Newer Older
1 2 3 4 5 6 7 8
"""Spawn a command with pipes to its stdin, stdout, and optionally stderr.

The normal os.popen(cmd, mode) call spawns a shell command and provides a
file interface to just the input or output of the process depending on
whether mode is 'r' or 'w'.  This module provides the functions popen2(cmd)
and popen3(cmd) which return two or three pipes to the spawned command.
"""

Guido van Rossum's avatar
Guido van Rossum committed
9 10 11
import os
import sys

12 13
__all__ = ["popen2", "popen3", "popen4"]

14
MAXFD = 256     # Max number of file descriptors (os.getdtablesize()???)
Guido van Rossum's avatar
Guido van Rossum committed
15

16 17 18 19
_active = []

def _cleanup():
    for inst in _active[:]:
20
        inst.poll()
21 22

class Popen3:
23 24 25
    """Class representing a child process.  Normally instances are created
    by the factory functions popen2() and popen3()."""

26 27
    sts = -1                    # Child not completed yet

28
    def __init__(self, cmd, capturestderr=0, bufsize=-1):
29 30 31 32 33
        """The parameter 'cmd' is the shell command to execute in a
        sub-process.  The 'capturestderr' flag, if true, specifies that
        the object should capture standard error output of the child process.
        The default is false.  If the 'bufsize' parameter is specified, it
        specifies the size of the I/O buffers to/from the child process."""
34
        _cleanup()
35 36 37 38 39 40 41
        p2cread, p2cwrite = os.pipe()
        c2pread, c2pwrite = os.pipe()
        if capturestderr:
            errout, errin = os.pipe()
        self.pid = os.fork()
        if self.pid == 0:
            # Child
42 43
            os.dup2(p2cread, 0)
            os.dup2(c2pwrite, 1)
44
            if capturestderr:
45 46
                os.dup2(errin, 2)
            self._run_child(cmd)
47 48 49 50 51 52 53 54 55 56
        os.close(p2cread)
        self.tochild = os.fdopen(p2cwrite, 'w', bufsize)
        os.close(c2pwrite)
        self.fromchild = os.fdopen(c2pread, 'r', bufsize)
        if capturestderr:
            os.close(errin)
            self.childerr = os.fdopen(errout, 'r', bufsize)
        else:
            self.childerr = None
        _active.append(self)
57

58 59 60 61 62 63 64 65 66 67 68 69 70
    def _run_child(self, cmd):
        if type(cmd) == type(''):
            cmd = ['/bin/sh', '-c', cmd]
        for i in range(3, MAXFD):
            try:
                os.close(i)
            except:
                pass
        try:
            os.execvp(cmd[0], cmd)
        finally:
            os._exit(1)

71
    def poll(self):
72 73
        """Return the exit status of the child process if it has finished,
        or -1 if it hasn't finished yet."""
74 75 76 77 78 79 80 81 82
        if self.sts < 0:
            try:
                pid, sts = os.waitpid(self.pid, os.WNOHANG)
                if pid == self.pid:
                    self.sts = sts
                    _active.remove(self)
            except os.error:
                pass
        return self.sts
83

84
    def wait(self):
85
        """Wait for and return the exit status of the child process."""
86 87 88 89 90
        pid, sts = os.waitpid(self.pid, 0)
        if pid == self.pid:
            self.sts = sts
            _active.remove(self)
        return self.sts
91

92

93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
class Popen4(Popen3):
    childerr = None

    def __init__(self, cmd, bufsize=-1):
        _cleanup()
        p2cread, p2cwrite = os.pipe()
        c2pread, c2pwrite = os.pipe()
        self.pid = os.fork()
        if self.pid == 0:
            # Child
            os.dup2(p2cread, 0)
            os.dup2(c2pwrite, 1)
            os.dup2(c2pwrite, 2)
            self._run_child(cmd)
        os.close(p2cread)
        self.tochild = os.fdopen(p2cwrite, 'w', bufsize)
        os.close(c2pwrite)
        self.fromchild = os.fdopen(c2pread, 'r', bufsize)
        _active.append(self)


114
if sys.platform[:3] == "win":
115
    # Some things don't make sense on non-Unix platforms.
116
    del Popen3, Popen4
117 118

    def popen2(cmd, bufsize=-1, mode='t'):
119 120 121 122 123
        """Execute the shell command 'cmd' in a sub-process.  If 'bufsize' is
        specified, it sets the buffer size for the I/O pipes.  The file objects
        (child_stdout, child_stdin) are returned."""
        w, r = os.popen2(cmd, mode, bufsize)
        return r, w
124

125
    def popen3(cmd, bufsize=-1, mode='t'):
126 127 128 129 130
        """Execute the shell command 'cmd' in a sub-process.  If 'bufsize' is
        specified, it sets the buffer size for the I/O pipes.  The file objects
        (child_stdout, child_stdin, child_stderr) are returned."""
        w, r, e = os.popen3(cmd, mode, bufsize)
        return r, w, e
131 132 133 134 135 136 137

    def popen4(cmd, bufsize=-1, mode='t'):
        """Execute the shell command 'cmd' in a sub-process.  If 'bufsize' is
        specified, it sets the buffer size for the I/O pipes.  The file objects
        (child_stdout_stderr, child_stdin) are returned."""
        w, r = os.popen4(cmd, mode, bufsize)
        return r, w
138
else:
139 140 141 142 143 144 145 146
    def popen2(cmd, bufsize=-1, mode='t'):
        """Execute the shell command 'cmd' in a sub-process.  If 'bufsize' is
        specified, it sets the buffer size for the I/O pipes.  The file objects
        (child_stdout, child_stdin) are returned."""
        inst = Popen3(cmd, 0, bufsize)
        return inst.fromchild, inst.tochild

    def popen3(cmd, bufsize=-1, mode='t'):
147 148 149 150 151 152
        """Execute the shell command 'cmd' in a sub-process.  If 'bufsize' is
        specified, it sets the buffer size for the I/O pipes.  The file objects
        (child_stdout, child_stdin, child_stderr) are returned."""
        inst = Popen3(cmd, 1, bufsize)
        return inst.fromchild, inst.tochild, inst.childerr

153
    def popen4(cmd, bufsize=-1, mode='t'):
154 155 156
        """Execute the shell command 'cmd' in a sub-process.  If 'bufsize' is
        specified, it sets the buffer size for the I/O pipes.  The file objects
        (child_stdout_stderr, child_stdin) are returned."""
157 158
        inst = Popen4(cmd, bufsize)
        return inst.fromchild, inst.tochild
159

160
    __all__.extend(["Popen3", "Popen4"])
Tim Peters's avatar
Tim Peters committed
161

162
def _test():
163
    cmd  = "cat"
164
    teststr = "ab cd\n"
165 166
    if os.name == "nt":
        cmd = "more"
167 168 169 170
    # "more" doesn't act the same way across Windows flavors,
    # sometimes adding an extra newline at the start or the
    # end.  So we strip whitespace off both ends for comparison.
    expected = teststr.strip()
171
    print "testing popen2..."
172
    r, w = popen2(cmd)
173 174
    w.write(teststr)
    w.close()
175 176 177
    got = r.read()
    if got.strip() != expected:
        raise ValueError("wrote %s read %s" % (`teststr`, `got`))
178
    print "testing popen3..."
179
    try:
180
        r, w, e = popen3([cmd])
181
    except:
182
        r, w, e = popen3(cmd)
183 184
    w.write(teststr)
    w.close()
185 186 187 188 189 190
    got = r.read()
    if got.strip() != expected:
        raise ValueError("wrote %s read %s" % (`teststr`, `got`))
    got = e.read()
    if got:
        raise ValueError("unexected %s on stderr" % `got`)
191 192
    for inst in _active[:]:
        inst.wait()
193 194
    if _active:
        raise ValueError("_active not empty")
195 196 197 198
    print "All OK"

if __name__ == '__main__':
    _test()