|
1 | 1 | """Functions for executing one shell command and multiple, concurrent shell commands. |
2 | 2 |
|
3 | 3 | Repository: |
4 | | - https://github.com/microlinux/python-shell |
| 4 | + https://github.com/microlinux/python-shell |
5 | 5 |
|
6 | 6 | Author: |
7 | | - Todd Mueller <firstname@firstnamelastname.com> |
8 | | - |
9 | | -Version: |
10 | | - 2.0 20160926 |
| 7 | + Todd Mueller <firstname@firstnamelastname.com> |
11 | 8 |
|
12 | 9 | License: |
13 | | - This program is free software: you can redistribute it and/or modify it |
14 | | - under the terms of the GNU General Public License as published by the |
15 | | - Free Software Foundation, version 3. See https://www.gnu.org/licenses/. |
| 10 | + This program is free software: you can redistribute it and/or modify it |
| 11 | + under the terms of the GNU General Public License as published by the |
| 12 | + Free Software Foundation, version 3. See https://www.gnu.org/licenses/. |
16 | 13 |
|
17 | 14 | """ |
18 | 15 |
|
|
28 | 25 | """Namedtuple representing the result of a shell command. |
29 | 26 |
|
30 | 27 | Fields: |
31 | | - cmd (str): command executed |
32 | | - pid (int): process id |
33 | | - retval (int): return value |
34 | | - runtime (int): runtime in msecs |
35 | | - stdout (str): stdout buffer, horizontal/vertical whitespace stripped |
36 | | - stderr (str): stderr buffer, horizontal/vertical whitespace stripped |
| 28 | + cmd (str): command executed |
| 29 | + pid (int): process id |
| 30 | + retval (int): return value |
| 31 | + runtime (int): runtime in msecs |
| 32 | + stdout (str): stdout buffer, horizontal/vertical whitespace stripped |
| 33 | + stderr (str): stderr buffer, horizontal/vertical whitespace stripped |
37 | 34 |
|
38 | 35 | """ |
39 | 36 |
|
40 | 37 | def stripper(string): |
41 | | - """Sexily strips horizontal and vertical whitespace from a string. |
| 38 | + """Sexily strips horizontal and vertical whitespace from a string. |
42 | 39 |
|
43 | | - Args: |
44 | | - string (str): string to strip |
| 40 | + Args: |
| 41 | + string (str): string to strip |
45 | 42 |
|
46 | | - Returns: |
47 | | - str: stripped string |
| 43 | + Returns: |
| 44 | + str: stripped string |
48 | 45 |
|
49 | | - """ |
| 46 | + """ |
50 | 47 |
|
51 | | - lines = map(str.strip, string.splitlines()) |
| 48 | + lines = map(str.strip, string.splitlines()) |
52 | 49 |
|
53 | | - while lines and not lines[-1]: |
54 | | - lines.pop() |
| 50 | + while lines and not lines[-1]: |
| 51 | + lines.pop() |
55 | 52 |
|
56 | | - while lines and not lines[0]: |
57 | | - lines.pop(0) |
| 53 | + while lines and not lines[0]: |
| 54 | + lines.pop(0) |
58 | 55 |
|
59 | | - return '\n'.join(lines) |
| 56 | + return '\n'.join(lines) |
60 | 57 |
|
61 | 58 | def command(cmd, timeout=10, stdin=None): |
62 | | - """Executes one shell command and returns the result. |
| 59 | + """Executes one shell command and returns the result. |
63 | 60 |
|
64 | | - Args: |
65 | | - cmd (str): command to execute |
66 | | - timeout (int): timeout in seconds (10) |
67 | | - stdin (str): input (None) |
| 61 | + Args: |
| 62 | + cmd (str): command to execute |
| 63 | + timeout (int): timeout in seconds (10) |
| 64 | + stdin (str): input (None) |
68 | 65 |
|
69 | | - Returns: |
70 | | - CommandResult: result of command |
| 66 | + Returns: |
| 67 | + CommandResult: result of command |
71 | 68 |
|
72 | 69 | """ |
73 | 70 |
|
74 | | - kill = lambda this_proc: this_proc.kill() |
75 | | - pid = None |
76 | | - retval = None |
77 | | - runtime = None |
78 | | - stderr = None |
79 | | - stdout = None |
80 | | - |
81 | | - try: |
82 | | - start = time.time() * 1000.0 |
83 | | - proc = subprocess.Popen(shlex.split(cmd), |
84 | | - stdout=subprocess.PIPE, |
85 | | - stderr=subprocess.PIPE, |
86 | | - stdin=subprocess.PIPE, |
87 | | - close_fds=True, |
88 | | - shell=False) |
89 | | - |
90 | | - timer = threading.Timer(timeout, kill, [proc]) |
91 | | - |
92 | | - timer.start() |
93 | | - stdout, stderr = map(stripper, proc.communicate(input=stdin)) |
94 | | - timer.cancel() |
95 | | - |
96 | | - runtime = int((time.time() * 1000.0) - start) |
97 | | - except OSError: |
98 | | - retval = 127 |
99 | | - stderr = 'command not found' |
100 | | - except: |
101 | | - retval = 255 |
102 | | - stderr = traceback.format_exc() |
103 | | - finally: |
104 | | - if retval == None: |
105 | | - if proc.returncode == -9: |
106 | | - retval = 254 |
107 | | - stderr = 'command timed out' |
108 | | - else: |
109 | | - retval = proc.returncode |
110 | | - |
111 | | - pid = proc.pid |
112 | | - |
113 | | - return CommandResult(cmd, pid, retval, runtime, stdout, stderr) |
| 71 | + kill = lambda this_proc: this_proc.kill() |
| 72 | + pid = None |
| 73 | + retval = None |
| 74 | + runtime = None |
| 75 | + stderr = None |
| 76 | + stdout = None |
| 77 | + |
| 78 | + try: |
| 79 | + start = time.time() * 1000.0 |
| 80 | + proc = subprocess.Popen(shlex.split(cmd), |
| 81 | + stdout=subprocess.PIPE, |
| 82 | + stderr=subprocess.PIPE, |
| 83 | + stdin=subprocess.PIPE, |
| 84 | + close_fds=True, |
| 85 | + shell=False) |
| 86 | + |
| 87 | + timer = threading.Timer(timeout, kill, [proc]) |
| 88 | + |
| 89 | + timer.start() |
| 90 | + stdout, stderr = map(stripper, proc.communicate(input=stdin)) |
| 91 | + timer.cancel() |
| 92 | + |
| 93 | + runtime = int((time.time() * 1000.0) - start) |
| 94 | + except OSError: |
| 95 | + retval = 127 |
| 96 | + stderr = 'command not found' |
| 97 | + except: |
| 98 | + retval = 255 |
| 99 | + stderr = traceback.format_exc() |
| 100 | + finally: |
| 101 | + if retval == None: |
| 102 | + if proc.returncode == -9: |
| 103 | + retval = 254 |
| 104 | + stderr = 'command timed out' |
| 105 | + else: |
| 106 | + retval = proc.returncode |
| 107 | + |
| 108 | + pid = proc.pid |
| 109 | + |
| 110 | + return CommandResult(cmd, pid, retval, runtime, stdout, stderr) |
114 | 111 |
|
115 | 112 | def command_wrapper(args): |
116 | | - """Wrapper used my multi_command to pass command arguments as a list.""" |
| 113 | + """Wrapper used my multi_command to pass command arguments as a list.""" |
117 | 114 |
|
118 | | - return command(*args) |
| 115 | + return command(*args) |
119 | 116 |
|
120 | 117 | def multi_command(cmds, timeout=10, stdins=None, workers=4): |
121 | | - """Executes multiple shell commands concurrently and returns the results. |
| 118 | + """Executes multiple shell commands concurrently and returns the results. |
122 | 119 |
|
123 | | - If stdins is given, its length must match that of cmds. If commands do |
124 | | - not require input, their corresponding stdin field must be None. |
| 120 | + If stdins is given, its length must match that of cmds. If commands do |
| 121 | + not require input, their corresponding stdin field must be None. |
125 | 122 |
|
126 | | - Args: |
127 | | - cmds (list): commands to execute |
128 | | - timeout (int): timeout in seconds per command (10) |
129 | | - workers (int): max concurrency (4) |
130 | | - stdins (list): inputs (None) |
| 123 | + Args: |
| 124 | + cmds (list): commands to execute |
| 125 | + timeout (int): timeout in seconds per command (10) |
| 126 | + workers (int): max concurrency (4) |
| 127 | + stdins (list): inputs (None) |
131 | 128 |
|
132 | | - Returns: |
133 | | - CommandResult generator: results of commands in given order |
| 129 | + Returns: |
| 130 | + CommandResult generator: results of commands in given order |
134 | 131 |
|
135 | | - """ |
| 132 | + """ |
136 | 133 |
|
137 | | - num_cmds = len(cmds) |
| 134 | + num_cmds = len(cmds) |
138 | 135 |
|
139 | | - if stdins != None: |
140 | | - if len(stdins) != num_cmds: |
141 | | - raise RuntimeError("stdins length doesn't match cmds length") |
| 136 | + if stdins != None: |
| 137 | + if len(stdins) != num_cmds: |
| 138 | + raise RuntimeError("stdins length doesn't match cmds length") |
142 | 139 |
|
143 | | - if workers > num_cmds: |
144 | | - workers = num_cmds |
| 140 | + if workers > num_cmds: |
| 141 | + workers = num_cmds |
145 | 142 |
|
146 | | - for i in xrange(num_cmds): |
147 | | - try: |
148 | | - stdin = stdins[i] |
149 | | - except: |
150 | | - stdin = None |
| 143 | + for i in xrange(num_cmds): |
| 144 | + try: |
| 145 | + stdin = stdins[i] |
| 146 | + except: |
| 147 | + stdin = None |
151 | 148 |
|
152 | | - cmds[i] = (cmds[i], timeout, stdin) |
| 149 | + cmds[i] = (cmds[i], timeout, stdin) |
153 | 150 |
|
154 | | - pool = multiprocessing.Pool(processes=workers) |
155 | | - results = pool.imap(command_wrapper, cmds) |
| 151 | + pool = multiprocessing.Pool(processes=workers) |
| 152 | + results = pool.imap(command_wrapper, cmds) |
156 | 153 |
|
157 | | - pool.close() |
158 | | - pool.join() |
| 154 | + pool.close() |
| 155 | + pool.join() |
159 | 156 |
|
160 | | - return results |
| 157 | + return results |
161 | 158 |
|
162 | 159 | def test(single_cmds=['ls -l', 'uptime', 'blargh'], |
163 | 160 | multi_cmds=['ls -l', 'uptime', 'ping -c3 -i1 -W1 google.com']): |
164 | | - """Demonstrates command and multi_command functionality. |
165 | | -
|
166 | | - Args: |
167 | | - single_cmds (list): commands to run one at a time |
168 | | - multi_cmds (list): commands to run concurrently |
169 | | -
|
170 | | - """ |
171 | | - |
172 | | - print |
173 | | - print '-----------------------' |
174 | | - print 'Running single commands' |
175 | | - print '-----------------------' |
176 | | - |
177 | | - for cmd in single_cmds: |
178 | | - result = command(cmd) |
179 | | - print |
180 | | - print 'cmd: %s' % result.cmd |
181 | | - print 'pid: %s' % result.pid |
182 | | - print 'ret: %s' % result.retval |
183 | | - print 'run: %s' % result.runtime |
184 | | - print 'out:' |
185 | | - print result.stdout |
186 | | - print 'err:' |
187 | | - print result.stderr |
188 | | - |
189 | | - print |
190 | | - print '-------------------------' |
191 | | - print 'Running multiple commands' |
192 | | - print '-------------------------' |
193 | | - |
194 | | - for result in multi_command(multi_cmds): |
195 | | - print |
196 | | - print 'cmd: %s' % result.cmd |
197 | | - print 'pid: %s' % result.pid |
198 | | - print 'ret: %s' % result.retval |
199 | | - print 'run: %s' % result.runtime |
200 | | - print 'out:' |
201 | | - print result.stdout |
202 | | - print 'err:' |
203 | | - print result.stderr |
| 161 | + """Demonstrates command and multi_command functionality. |
| 162 | +
|
| 163 | + Args: |
| 164 | + single_cmds (list): commands to run one at a time |
| 165 | + multi_cmds (list): commands to run concurrently |
| 166 | +
|
| 167 | + """ |
| 168 | + |
| 169 | + print |
| 170 | + print '-----------------------' |
| 171 | + print 'Running single commands' |
| 172 | + print '-----------------------' |
| 173 | + |
| 174 | + for cmd in single_cmds: |
| 175 | + result = command(cmd) |
| 176 | + print |
| 177 | + print 'cmd: %s' % result.cmd |
| 178 | + print 'pid: %s' % result.pid |
| 179 | + print 'ret: %s' % result.retval |
| 180 | + print 'run: %s' % result.runtime |
| 181 | + print 'out:' |
| 182 | + print result.stdout |
| 183 | + print 'err:' |
| 184 | + print result.stderr |
| 185 | + |
| 186 | + print |
| 187 | + print '-------------------------' |
| 188 | + print 'Running multiple commands' |
| 189 | + print '-------------------------' |
| 190 | + |
| 191 | + for result in multi_command(multi_cmds): |
| 192 | + print |
| 193 | + print 'cmd: %s' % result.cmd |
| 194 | + print 'pid: %s' % result.pid |
| 195 | + print 'ret: %s' % result.retval |
| 196 | + print 'run: %s' % result.runtime |
| 197 | + print 'out:' |
| 198 | + print result.stdout |
| 199 | + print 'err:' |
| 200 | + print result.stderr |
0 commit comments