2 # Copyright 1999-2011 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
12 here_doc_re = re.compile(r'.*\s<<[-]?(\w+)$')
13 func_start_re = re.compile(r'^[-\w]+\s*\(\)\s*$')
14 func_end_re = re.compile(r'^\}$')
16 var_assign_re = re.compile(r'(^|^declare\s+-\S+\s+|^declare\s+|^export\s+)([^=\s]+)=("|\')?.*$')
17 close_quote_re = re.compile(r'(\\"|"|\')\s*$')
18 readonly_re = re.compile(r'^declare\s+-(\S*)r(\S*)\s+')
19 # declare without assignment
20 var_declare_re = re.compile(r'^declare(\s+-\S+)?\s+([^=\s]+)\s*$')
22 def have_end_quote(quote, line):
24 Check if the line has an end quote (useful for handling multi-line
25 quotes). This handles escaped double quotes that may occur at the
26 end of a line. The posix spec does not allow escaping of single
27 quotes inside of single quotes, so that case is not handled.
29 close_quote_match = close_quote_re.search(line)
30 return close_quote_match is not None and \
31 close_quote_match.group(1) == quote
33 def filter_declare_readonly_opt(line):
34 readonly_match = readonly_re.match(line)
35 if readonly_match is not None:
38 group = readonly_match.group(i)
42 line = 'declare -%s %s' % \
43 (declare_opts, line[readonly_match.end():])
45 line = 'declare ' + line[readonly_match.end():]
48 def filter_bash_environment(pattern, file_in, file_out):
49 # Filter out any instances of the \1 character from variable values
50 # since this character multiplies each time that the environment
51 # is saved (strange bash behavior). This can eventually result in
52 # mysterious 'Argument list too long' errors from programs that have
53 # huge strings of \1 characters in their environment. See bug #222091.
56 multi_line_quote = None
57 multi_line_quote_filter = None
59 if multi_line_quote is not None:
60 if not multi_line_quote_filter:
61 file_out.write(line.replace("\1", ""))
62 if have_end_quote(multi_line_quote, line):
63 multi_line_quote = None
64 multi_line_quote_filter = None
66 if here_doc_delim is None and in_func is None:
67 var_assign_match = var_assign_re.match(line)
68 if var_assign_match is not None:
69 quote = var_assign_match.group(3)
70 filter_this = pattern.match(var_assign_match.group(2)) \
72 # Exclude the start quote when searching for the end quote,
73 # to ensure that the start quote is not misidentified as the
74 # end quote (happens if there is a newline immediately after
76 if quote is not None and not \
77 have_end_quote(quote, line[var_assign_match.end(2)+2:]):
78 multi_line_quote = quote
79 multi_line_quote_filter = filter_this
81 line = filter_declare_readonly_opt(line)
82 file_out.write(line.replace("\1", ""))
85 declare_match = var_declare_re.match(line)
86 if declare_match is not None:
87 # declare without assignment
88 filter_this = pattern.match(declare_match.group(2)) \
91 line = filter_declare_readonly_opt(line)
95 if here_doc_delim is not None:
96 if here_doc_delim.match(line):
100 here_doc = here_doc_re.match(line)
101 if here_doc is not None:
102 here_doc_delim = re.compile("^%s$" % here_doc.group(1))
105 # Note: here-documents are handled before functions since otherwise
106 # it would be possible for the content of a here-document to be
107 # mistaken as the end of a function.
109 if func_end_re.match(line) is not None:
113 in_func = func_start_re.match(line)
114 if in_func is not None:
117 # This line is not recognized as part of a variable assignment,
118 # function definition, or here document, so just allow it to
122 if __name__ == "__main__":
123 description = "Filter out variable assignments for variable " + \
124 "names matching a given PATTERN " + \
125 "while leaving bash function definitions and here-documents " + \
126 "intact. The PATTERN is a space separated list of variable names" + \
127 " and it supports python regular expression syntax."
128 usage = "usage: %s PATTERN" % os.path.basename(sys.argv[0])
129 parser = optparse.OptionParser(description=description, usage=usage)
130 options, args = parser.parse_args(sys.argv[1:])
132 parser.error("Missing required PATTERN argument.")
134 file_out = sys.stdout
135 if sys.hexversion >= 0x3000000:
136 file_in = codecs.iterdecode(sys.stdin.buffer.raw,
137 'utf_8', errors='replace')
138 file_out = io.TextIOWrapper(sys.stdout.buffer,
139 'utf_8', errors='backslashreplace')
141 var_pattern = args[0].split()
143 # Filter invalid variable names that are not supported by bash.
144 var_pattern.append(r'\d.*')
145 var_pattern.append(r'.*\W.*')
147 var_pattern = "^(%s)$" % "|".join(var_pattern)
148 filter_bash_environment(
149 re.compile(var_pattern), file_in, file_out)