1# Copyright 2016 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Functions for working with shell code.""" 16 17import os 18import sys 19 20_path = os.path.realpath(__file__ + '/../..') 21if sys.path[0] != _path: 22 sys.path.insert(0, _path) 23del _path 24 25 26# For use by ShellQuote. Match all characters that the shell might treat 27# specially. This means a number of things: 28# - Reserved characters. 29# - Characters used in expansions (brace, variable, path, globs, etc...). 30# - Characters that an interactive shell might use (like !). 31# - Whitespace so that one arg turns into multiple. 32# See the bash man page as well as the POSIX shell documentation for more info: 33# http://www.gnu.org/software/bash/manual/bashref.html 34# http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html 35_SHELL_QUOTABLE_CHARS = frozenset('[|&;()<> \t!{}[]=*?~$"\'\\#^') 36# The chars that, when used inside of double quotes, need escaping. 37# Order here matters as we need to escape backslashes first. 38_SHELL_ESCAPE_CHARS = r'\"`$' 39 40 41def shell_quote(s): 42 """Quote |s| in a way that is safe for use in a shell. 43 44 We aim to be safe, but also to produce "nice" output. That means we don't 45 use quotes when we don't need to, and we prefer to use less quotes (like 46 putting it all in single quotes) than more (using double quotes and escaping 47 a bunch of stuff, or mixing the quotes). 48 49 While python does provide a number of alternatives like: 50 - pipes.quote 51 - shlex.quote 52 They suffer from various problems like: 53 - Not widely available in different python versions. 54 - Do not produce pretty output in many cases. 55 - Are in modules that rarely otherwise get used. 56 57 Note: We don't handle reserved shell words like "for" or "case". This is 58 because those only matter when they're the first element in a command, and 59 there is no use case for that. When we want to run commands, we tend to 60 run real programs and not shell ones. 61 62 Args: 63 s: The string to quote. 64 65 Returns: 66 A safely (possibly quoted) string. 67 """ 68 if isinstance(s, bytes): 69 s = s.encode('utf-8') 70 71 # See if no quoting is needed so we can return the string as-is. 72 for c in s: 73 if c in _SHELL_QUOTABLE_CHARS: 74 break 75 else: 76 return s if s else u"''" 77 78 # See if we can use single quotes first. Output is nicer. 79 if "'" not in s: 80 return u"'%s'" % s 81 82 # Have to use double quotes. Escape the few chars that still expand when 83 # used inside of double quotes. 84 for c in _SHELL_ESCAPE_CHARS: 85 if c in s: 86 s = s.replace(c, r'\%s' % c) 87 return u'"%s"' % s 88 89 90def shell_unquote(s): 91 """Do the opposite of ShellQuote. 92 93 This function assumes that the input is a valid escaped string. 94 The behaviour is undefined on malformed strings. 95 96 Args: 97 s: An escaped string. 98 99 Returns: 100 The unescaped version of the string. 101 """ 102 if not s: 103 return '' 104 105 if s[0] == "'": 106 return s[1:-1] 107 108 if s[0] != '"': 109 return s 110 111 s = s[1:-1] 112 output = '' 113 i = 0 114 while i < len(s) - 1: 115 # Skip the backslash when it makes sense. 116 if s[i] == '\\' and s[i + 1] in _SHELL_ESCAPE_CHARS: 117 i += 1 118 output += s[i] 119 i += 1 120 return output + s[i] if i < len(s) else output 121 122 123def cmd_to_str(cmd): 124 """Translate a command list into a space-separated string. 125 126 The resulting string should be suitable for logging messages and for 127 pasting into a terminal to run. Command arguments are surrounded by 128 quotes to keep them grouped, even if an argument has spaces in it. 129 130 Examples: 131 ['a', 'b'] ==> "'a' 'b'" 132 ['a b', 'c'] ==> "'a b' 'c'" 133 ['a', 'b\'c'] ==> '\'a\' "b\'c"' 134 [u'a', "/'$b"] ==> '\'a\' "/\'$b"' 135 [] ==> '' 136 See unittest for additional (tested) examples. 137 138 Args: 139 cmd: List of command arguments. 140 141 Returns: 142 String representing full command. 143 """ 144 # Use str before repr to translate unicode strings to regular strings. 145 return ' '.join(shell_quote(arg) for arg in cmd) 146 147 148def boolean_shell_value(sval, default): 149 """See if |sval| is a value users typically consider as boolean.""" 150 if sval is None: 151 return default 152 153 if isinstance(sval, str): 154 s = sval.lower() 155 if s in ('yes', 'y', '1', 'true'): 156 return True 157 if s in ('no', 'n', '0', 'false'): 158 return False 159 160 raise ValueError('Could not decode as a boolean value: %r' % (sval,)) 161