2 # Copyright 1999-2014 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
11 here_doc_re = re.compile(r'.*\s<<[-]?(\w+)$')
12 func_start_re = re.compile(r'^[-\w]+\s*\(\)\s*$')
13 func_end_re = re.compile(r'^\}$')
15 var_assign_re = re.compile(r'(^|^declare\s+-\S+\s+|^declare\s+|^export\s+)([^=\s]+)=("|\')?.*$')
16 close_quote_re = re.compile(r'(\\"|"|\')\s*$')
17 readonly_re = re.compile(r'^declare\s+-(\S*)r(\S*)\s+')
18 # declare without assignment
19 var_declare_re = re.compile(r'^declare(\s+-\S+)?\s+([^=\s]+)\s*$')
21 def have_end_quote(quote, line):
23 Check if the line has an end quote (useful for handling multi-line
24 quotes). This handles escaped double quotes that may occur at the
25 end of a line. The posix spec does not allow escaping of single
26 quotes inside of single quotes, so that case is not handled.
28 close_quote_match = close_quote_re.search(line)
29 return close_quote_match is not None and \
30 close_quote_match.group(1) == quote
32 def filter_declare_readonly_opt(line):
33 readonly_match = readonly_re.match(line)
34 if readonly_match is not None:
37 group = readonly_match.group(i)
41 line = 'declare -%s %s' % \
42 (declare_opts, line[readonly_match.end():])
44 line = 'declare ' + line[readonly_match.end():]
47 def filter_bash_environment(pattern, file_in, file_out):
48 # Filter out any instances of the \1 character from variable values
49 # since this character multiplies each time that the environment
50 # is saved (strange bash behavior). This can eventually result in
51 # mysterious 'Argument list too long' errors from programs that have
52 # huge strings of \1 characters in their environment. See bug #222091.
55 multi_line_quote = None
56 multi_line_quote_filter = None
58 if multi_line_quote is not None:
59 if not multi_line_quote_filter:
60 file_out.write(line.replace("\1", ""))
61 if have_end_quote(multi_line_quote, line):
62 multi_line_quote = None
63 multi_line_quote_filter = None
65 if here_doc_delim is None and in_func is None:
66 var_assign_match = var_assign_re.match(line)
67 if var_assign_match is not None:
68 quote = var_assign_match.group(3)
69 filter_this = pattern.match(var_assign_match.group(2)) \
71 # Exclude the start quote when searching for the end quote,
72 # to ensure that the start quote is not misidentified as the
73 # end quote (happens if there is a newline immediately after
75 if quote is not None and not \
76 have_end_quote(quote, line[var_assign_match.end(2)+2:]):
77 multi_line_quote = quote
78 multi_line_quote_filter = filter_this
80 line = filter_declare_readonly_opt(line)
81 file_out.write(line.replace("\1", ""))
84 declare_match = var_declare_re.match(line)
85 if declare_match is not None:
86 # declare without assignment
87 filter_this = pattern.match(declare_match.group(2)) \
90 line = filter_declare_readonly_opt(line)
94 if here_doc_delim is not None:
95 if here_doc_delim.match(line):
99 here_doc = here_doc_re.match(line)
100 if here_doc is not None:
101 here_doc_delim = re.compile("^%s$" % here_doc.group(1))
104 # Note: here-documents are handled before functions since otherwise
105 # it would be possible for the content of a here-document to be
106 # mistaken as the end of a function.
108 if func_end_re.match(line) is not None:
112 in_func = func_start_re.match(line)
113 if in_func is not None:
116 # This line is not recognized as part of a variable assignment,
117 # function definition, or here document, so just allow it to
121 if __name__ == "__main__":
122 description = "Filter out variable assignments for variable " + \
123 "names matching a given PATTERN " + \
124 "while leaving bash function definitions and here-documents " + \
125 "intact. The PATTERN is a space separated list of variable names" + \
126 " and it supports python regular expression syntax."
127 usage = "usage: %s PATTERN" % os.path.basename(sys.argv[0])
130 if '-h' in args or '--help' in args:
131 sys.stdout.write(usage + "\n")
136 sys.stderr.write(usage + "\n")
137 sys.stderr.write("Exactly one PATTERN argument required.\n")
142 file_out = sys.stdout
143 if sys.hexversion >= 0x3000000:
144 file_in = codecs.iterdecode(sys.stdin.buffer.raw,
145 'utf_8', errors='replace')
146 file_out = io.TextIOWrapper(sys.stdout.buffer,
147 'utf_8', errors='backslashreplace')
149 var_pattern = args[0].split()
151 # Filter invalid variable names that are not supported by bash.
152 var_pattern.append(r'\d.*')
153 var_pattern.append(r'.*\W.*')
155 var_pattern = "^(%s)$" % "|".join(var_pattern)
156 filter_bash_environment(
157 re.compile(var_pattern), file_in, file_out)