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 |
12 import StringIO | 13 import StringIO |
13 import subprocess | 14 import subprocess |
14 import time | 15 import time |
15 | 16 |
16 # fcntl is not available on Windows. | 17 # fcntl is not available on Windows. |
17 try: | 18 try: |
18 import fcntl | 19 import fcntl |
19 except ImportError: | 20 except ImportError: |
20 fcntl = None | 21 fcntl = None |
21 | 22 |
| 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 |
22 | 69 |
23 def Popen(args, stdout=None, stderr=None, shell=None, cwd=None, env=None): | 70 def Popen(args, stdout=None, stderr=None, shell=None, cwd=None, env=None): |
24 return subprocess.Popen( | 71 return subprocess.Popen( |
25 args=args, cwd=cwd, stdout=stdout, stderr=stderr, | 72 args=args, cwd=cwd, stdout=stdout, stderr=stderr, |
26 shell=shell, close_fds=True, env=env, | 73 shell=shell, close_fds=True, env=env, |
27 preexec_fn=lambda: signal.signal(signal.SIGPIPE, signal.SIG_DFL)) | 74 preexec_fn=lambda: signal.signal(signal.SIGPIPE, signal.SIG_DFL)) |
28 | 75 |
29 | 76 |
30 def Call(args, stdout=None, stderr=None, shell=None, cwd=None, env=None): | 77 def Call(args, stdout=None, stderr=None, shell=None, cwd=None, env=None): |
31 pipe = Popen(args, stdout=stdout, stderr=stderr, shell=shell, cwd=cwd, | 78 pipe = Popen(args, stdout=stdout, stderr=stderr, shell=shell, cwd=cwd, |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
81 Returns: | 128 Returns: |
82 The 2-tuple (exit code, output). | 129 The 2-tuple (exit code, output). |
83 """ | 130 """ |
84 if isinstance(args, basestring): | 131 if isinstance(args, basestring): |
85 args_repr = args | 132 args_repr = args |
86 if not shell: | 133 if not shell: |
87 raise Exception('string args must be run with shell=True') | 134 raise Exception('string args must be run with shell=True') |
88 elif shell: | 135 elif shell: |
89 raise Exception('array args must be run with shell=False') | 136 raise Exception('array args must be run with shell=False') |
90 else: | 137 else: |
91 args_repr = ' '.join(map(pipes.quote, args)) | 138 args_repr = ' '.join(map(SingleQuote, args)) |
92 | 139 |
93 s = '[host]' | 140 s = '[host]' |
94 if cwd: | 141 if cwd: |
95 s += ':' + cwd | 142 s += ':' + cwd |
96 s += '> ' + args_repr | 143 s += '> ' + args_repr |
97 logging.info(s) | 144 logging.info(s) |
98 pipe = Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, | 145 pipe = Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
99 shell=shell, cwd=cwd) | 146 shell=shell, cwd=cwd) |
100 stdout, stderr = pipe.communicate() | 147 stdout, stderr = pipe.communicate() |
101 | 148 |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
158 break | 205 break |
159 finally: | 206 finally: |
160 try: | 207 try: |
161 # Make sure the process doesn't stick around if we fail with an | 208 # Make sure the process doesn't stick around if we fail with an |
162 # exception. | 209 # exception. |
163 process.kill() | 210 process.kill() |
164 except OSError: | 211 except OSError: |
165 pass | 212 pass |
166 process.wait() | 213 process.wait() |
167 return process.returncode, output.getvalue() | 214 return process.returncode, output.getvalue() |
OLD | NEW |