OLD | NEW |
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """A wrapper for subprocess to make calling shell commands easier.""" | 5 """A wrapper for subprocess to make calling shell commands easier.""" |
6 | 6 |
7 import logging | 7 import logging |
8 import os | 8 import os |
9 import pipes | 9 import pipes |
10 import select | 10 import select |
11 import signal | 11 import signal |
12 import string | |
13 import StringIO | 12 import StringIO |
14 import subprocess | 13 import subprocess |
15 import time | 14 import time |
16 | 15 |
17 # fcntl is not available on Windows. | 16 # fcntl is not available on Windows. |
18 try: | 17 try: |
19 import fcntl | 18 import fcntl |
20 except ImportError: | 19 except ImportError: |
21 fcntl = None | 20 fcntl = None |
22 | 21 |
23 _SafeShellChars = frozenset(string.ascii_letters + string.digits + '@%_-+=:,./') | |
24 | |
25 def SingleQuote(s): | |
26 """Return an shell-escaped version of the string using single quotes. | |
27 | |
28 Reliably quote a string which may contain unsafe characters (e.g. space, | |
29 quote, or other special characters such as '$'). | |
30 | |
31 The returned value can be used in a shell command line as one token that gets | |
32 to be interpreted literally. | |
33 | |
34 Args: | |
35 s: The string to quote. | |
36 | |
37 Return: | |
38 The string quoted using single quotes. | |
39 """ | |
40 return pipes.quote(s) | |
41 | |
42 def DoubleQuote(s): | |
43 """Return an shell-escaped version of the string using double quotes. | |
44 | |
45 Reliably quote a string which may contain unsafe characters (e.g. space | |
46 or quote characters), while retaining some shell features such as variable | |
47 interpolation. | |
48 | |
49 The returned value can be used in a shell command line as one token that gets | |
50 to be further interpreted by the shell. | |
51 | |
52 The set of characters that retain their special meaning may depend on the | |
53 shell implementation. This set usually includes: '$', '`', '\', '!', '*', | |
54 and '@'. | |
55 | |
56 Args: | |
57 s: The string to quote. | |
58 | |
59 Return: | |
60 The string quoted using double quotes. | |
61 """ | |
62 if not s: | |
63 return '""' | |
64 elif all(c in _SafeShellChars for c in s): | |
65 return s | |
66 else: | |
67 return '"' + s.replace('"', '\\"') + '"' | |
68 | |
69 | 22 |
70 def Popen(args, stdout=None, stderr=None, shell=None, cwd=None, env=None): | 23 def Popen(args, stdout=None, stderr=None, shell=None, cwd=None, env=None): |
71 return subprocess.Popen( | 24 return subprocess.Popen( |
72 args=args, cwd=cwd, stdout=stdout, stderr=stderr, | 25 args=args, cwd=cwd, stdout=stdout, stderr=stderr, |
73 shell=shell, close_fds=True, env=env, | 26 shell=shell, close_fds=True, env=env, |
74 preexec_fn=lambda: signal.signal(signal.SIGPIPE, signal.SIG_DFL)) | 27 preexec_fn=lambda: signal.signal(signal.SIGPIPE, signal.SIG_DFL)) |
75 | 28 |
76 | 29 |
77 def Call(args, stdout=None, stderr=None, shell=None, cwd=None, env=None): | 30 def Call(args, stdout=None, stderr=None, shell=None, cwd=None, env=None): |
78 pipe = Popen(args, stdout=stdout, stderr=stderr, shell=shell, cwd=cwd, | 31 pipe = Popen(args, stdout=stdout, stderr=stderr, shell=shell, cwd=cwd, |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
128 Returns: | 81 Returns: |
129 The 2-tuple (exit code, output). | 82 The 2-tuple (exit code, output). |
130 """ | 83 """ |
131 if isinstance(args, basestring): | 84 if isinstance(args, basestring): |
132 args_repr = args | 85 args_repr = args |
133 if not shell: | 86 if not shell: |
134 raise Exception('string args must be run with shell=True') | 87 raise Exception('string args must be run with shell=True') |
135 elif shell: | 88 elif shell: |
136 raise Exception('array args must be run with shell=False') | 89 raise Exception('array args must be run with shell=False') |
137 else: | 90 else: |
138 args_repr = ' '.join(map(SingleQuote, args)) | 91 args_repr = ' '.join(map(pipes.quote, args)) |
139 | 92 |
140 s = '[host]' | 93 s = '[host]' |
141 if cwd: | 94 if cwd: |
142 s += ':' + cwd | 95 s += ':' + cwd |
143 s += '> ' + args_repr | 96 s += '> ' + args_repr |
144 logging.info(s) | 97 logging.info(s) |
145 pipe = Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, | 98 pipe = Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
146 shell=shell, cwd=cwd) | 99 shell=shell, cwd=cwd) |
147 stdout, stderr = pipe.communicate() | 100 stdout, stderr = pipe.communicate() |
148 | 101 |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
205 break | 158 break |
206 finally: | 159 finally: |
207 try: | 160 try: |
208 # Make sure the process doesn't stick around if we fail with an | 161 # Make sure the process doesn't stick around if we fail with an |
209 # exception. | 162 # exception. |
210 process.kill() | 163 process.kill() |
211 except OSError: | 164 except OSError: |
212 pass | 165 pass |
213 process.wait() | 166 process.wait() |
214 return process.returncode, output.getvalue() | 167 return process.returncode, output.getvalue() |
OLD | NEW |