Commit | Line | Data |
---|---|---|
920dae64 AT |
1 | """Spawn a command with pipes to its stdin, stdout, and optionally stderr. |
2 | ||
3 | The normal os.popen(cmd, mode) call spawns a shell command and provides a | |
4 | file interface to just the input or output of the process depending on | |
5 | whether mode is 'r' or 'w'. This module provides the functions popen2(cmd) | |
6 | and popen3(cmd) which return two or three pipes to the spawned command. | |
7 | """ | |
8 | ||
9 | import os | |
10 | import sys | |
11 | ||
12 | __all__ = ["popen2", "popen3", "popen4"] | |
13 | ||
14 | try: | |
15 | MAXFD = os.sysconf('SC_OPEN_MAX') | |
16 | except (AttributeError, ValueError): | |
17 | MAXFD = 256 | |
18 | ||
19 | _active = [] | |
20 | ||
21 | def _cleanup(): | |
22 | for inst in _active[:]: | |
23 | inst.poll() | |
24 | ||
25 | class Popen3: | |
26 | """Class representing a child process. Normally instances are created | |
27 | by the factory functions popen2() and popen3().""" | |
28 | ||
29 | sts = -1 # Child not completed yet | |
30 | ||
31 | def __init__(self, cmd, capturestderr=False, bufsize=-1): | |
32 | """The parameter 'cmd' is the shell command to execute in a | |
33 | sub-process. On UNIX, 'cmd' may be a sequence, in which case arguments | |
34 | will be passed directly to the program without shell intervention (as | |
35 | with os.spawnv()). If 'cmd' is a string it will be passed to the shell | |
36 | (as with os.system()). The 'capturestderr' flag, if true, specifies | |
37 | that the object should capture standard error output of the child | |
38 | process. The default is false. If the 'bufsize' parameter is | |
39 | specified, it specifies the size of the I/O buffers to/from the child | |
40 | process.""" | |
41 | _cleanup() | |
42 | p2cread, p2cwrite = os.pipe() | |
43 | c2pread, c2pwrite = os.pipe() | |
44 | if capturestderr: | |
45 | errout, errin = os.pipe() | |
46 | self.pid = os.fork() | |
47 | if self.pid == 0: | |
48 | # Child | |
49 | os.dup2(p2cread, 0) | |
50 | os.dup2(c2pwrite, 1) | |
51 | if capturestderr: | |
52 | os.dup2(errin, 2) | |
53 | self._run_child(cmd) | |
54 | os.close(p2cread) | |
55 | self.tochild = os.fdopen(p2cwrite, 'w', bufsize) | |
56 | os.close(c2pwrite) | |
57 | self.fromchild = os.fdopen(c2pread, 'r', bufsize) | |
58 | if capturestderr: | |
59 | os.close(errin) | |
60 | self.childerr = os.fdopen(errout, 'r', bufsize) | |
61 | else: | |
62 | self.childerr = None | |
63 | _active.append(self) | |
64 | ||
65 | def _run_child(self, cmd): | |
66 | if isinstance(cmd, basestring): | |
67 | cmd = ['/bin/sh', '-c', cmd] | |
68 | for i in range(3, MAXFD): | |
69 | try: | |
70 | os.close(i) | |
71 | except OSError: | |
72 | pass | |
73 | try: | |
74 | os.execvp(cmd[0], cmd) | |
75 | finally: | |
76 | os._exit(1) | |
77 | ||
78 | def poll(self): | |
79 | """Return the exit status of the child process if it has finished, | |
80 | or -1 if it hasn't finished yet.""" | |
81 | if self.sts < 0: | |
82 | try: | |
83 | pid, sts = os.waitpid(self.pid, os.WNOHANG) | |
84 | if pid == self.pid: | |
85 | self.sts = sts | |
86 | _active.remove(self) | |
87 | except os.error: | |
88 | pass | |
89 | return self.sts | |
90 | ||
91 | def wait(self): | |
92 | """Wait for and return the exit status of the child process.""" | |
93 | if self.sts < 0: | |
94 | pid, sts = os.waitpid(self.pid, 0) | |
95 | if pid == self.pid: | |
96 | self.sts = sts | |
97 | _active.remove(self) | |
98 | return self.sts | |
99 | ||
100 | ||
101 | class Popen4(Popen3): | |
102 | childerr = None | |
103 | ||
104 | def __init__(self, cmd, bufsize=-1): | |
105 | _cleanup() | |
106 | p2cread, p2cwrite = os.pipe() | |
107 | c2pread, c2pwrite = os.pipe() | |
108 | self.pid = os.fork() | |
109 | if self.pid == 0: | |
110 | # Child | |
111 | os.dup2(p2cread, 0) | |
112 | os.dup2(c2pwrite, 1) | |
113 | os.dup2(c2pwrite, 2) | |
114 | self._run_child(cmd) | |
115 | os.close(p2cread) | |
116 | self.tochild = os.fdopen(p2cwrite, 'w', bufsize) | |
117 | os.close(c2pwrite) | |
118 | self.fromchild = os.fdopen(c2pread, 'r', bufsize) | |
119 | _active.append(self) | |
120 | ||
121 | ||
122 | if sys.platform[:3] == "win" or sys.platform == "os2emx": | |
123 | # Some things don't make sense on non-Unix platforms. | |
124 | del Popen3, Popen4 | |
125 | ||
126 | def popen2(cmd, bufsize=-1, mode='t'): | |
127 | """Execute the shell command 'cmd' in a sub-process. On UNIX, 'cmd' may | |
128 | be a sequence, in which case arguments will be passed directly to the | |
129 | program without shell intervention (as with os.spawnv()). If 'cmd' is a | |
130 | string it will be passed to the shell (as with os.system()). If | |
131 | 'bufsize' is specified, it sets the buffer size for the I/O pipes. The | |
132 | file objects (child_stdout, child_stdin) are returned.""" | |
133 | w, r = os.popen2(cmd, mode, bufsize) | |
134 | return r, w | |
135 | ||
136 | def popen3(cmd, bufsize=-1, mode='t'): | |
137 | """Execute the shell command 'cmd' in a sub-process. On UNIX, 'cmd' may | |
138 | be a sequence, in which case arguments will be passed directly to the | |
139 | program without shell intervention (as with os.spawnv()). If 'cmd' is a | |
140 | string it will be passed to the shell (as with os.system()). If | |
141 | 'bufsize' is specified, it sets the buffer size for the I/O pipes. The | |
142 | file objects (child_stdout, child_stdin, child_stderr) are returned.""" | |
143 | w, r, e = os.popen3(cmd, mode, bufsize) | |
144 | return r, w, e | |
145 | ||
146 | def popen4(cmd, bufsize=-1, mode='t'): | |
147 | """Execute the shell command 'cmd' in a sub-process. On UNIX, 'cmd' may | |
148 | be a sequence, in which case arguments will be passed directly to the | |
149 | program without shell intervention (as with os.spawnv()). If 'cmd' is a | |
150 | string it will be passed to the shell (as with os.system()). If | |
151 | 'bufsize' is specified, it sets the buffer size for the I/O pipes. The | |
152 | file objects (child_stdout_stderr, child_stdin) are returned.""" | |
153 | w, r = os.popen4(cmd, mode, bufsize) | |
154 | return r, w | |
155 | else: | |
156 | def popen2(cmd, bufsize=-1, mode='t'): | |
157 | """Execute the shell command 'cmd' in a sub-process. On UNIX, 'cmd' may | |
158 | be a sequence, in which case arguments will be passed directly to the | |
159 | program without shell intervention (as with os.spawnv()). If 'cmd' is a | |
160 | string it will be passed to the shell (as with os.system()). If | |
161 | 'bufsize' is specified, it sets the buffer size for the I/O pipes. The | |
162 | file objects (child_stdout, child_stdin) are returned.""" | |
163 | inst = Popen3(cmd, False, bufsize) | |
164 | return inst.fromchild, inst.tochild | |
165 | ||
166 | def popen3(cmd, bufsize=-1, mode='t'): | |
167 | """Execute the shell command 'cmd' in a sub-process. On UNIX, 'cmd' may | |
168 | be a sequence, in which case arguments will be passed directly to the | |
169 | program without shell intervention (as with os.spawnv()). If 'cmd' is a | |
170 | string it will be passed to the shell (as with os.system()). If | |
171 | 'bufsize' is specified, it sets the buffer size for the I/O pipes. The | |
172 | file objects (child_stdout, child_stdin, child_stderr) are returned.""" | |
173 | inst = Popen3(cmd, True, bufsize) | |
174 | return inst.fromchild, inst.tochild, inst.childerr | |
175 | ||
176 | def popen4(cmd, bufsize=-1, mode='t'): | |
177 | """Execute the shell command 'cmd' in a sub-process. On UNIX, 'cmd' may | |
178 | be a sequence, in which case arguments will be passed directly to the | |
179 | program without shell intervention (as with os.spawnv()). If 'cmd' is a | |
180 | string it will be passed to the shell (as with os.system()). If | |
181 | 'bufsize' is specified, it sets the buffer size for the I/O pipes. The | |
182 | file objects (child_stdout_stderr, child_stdin) are returned.""" | |
183 | inst = Popen4(cmd, bufsize) | |
184 | return inst.fromchild, inst.tochild | |
185 | ||
186 | __all__.extend(["Popen3", "Popen4"]) | |
187 | ||
188 | def _test(): | |
189 | cmd = "cat" | |
190 | teststr = "ab cd\n" | |
191 | if os.name == "nt": | |
192 | cmd = "more" | |
193 | # "more" doesn't act the same way across Windows flavors, | |
194 | # sometimes adding an extra newline at the start or the | |
195 | # end. So we strip whitespace off both ends for comparison. | |
196 | expected = teststr.strip() | |
197 | print "testing popen2..." | |
198 | r, w = popen2(cmd) | |
199 | w.write(teststr) | |
200 | w.close() | |
201 | got = r.read() | |
202 | if got.strip() != expected: | |
203 | raise ValueError("wrote %r read %r" % (teststr, got)) | |
204 | print "testing popen3..." | |
205 | try: | |
206 | r, w, e = popen3([cmd]) | |
207 | except: | |
208 | r, w, e = popen3(cmd) | |
209 | w.write(teststr) | |
210 | w.close() | |
211 | got = r.read() | |
212 | if got.strip() != expected: | |
213 | raise ValueError("wrote %r read %r" % (teststr, got)) | |
214 | got = e.read() | |
215 | if got: | |
216 | raise ValueError("unexpected %r on stderr" % (got,)) | |
217 | for inst in _active[:]: | |
218 | inst.wait() | |
219 | if _active: | |
220 | raise ValueError("_active not empty") | |
221 | print "All OK" | |
222 | ||
223 | if __name__ == '__main__': | |
224 | _test() |