| #!/usr/bin/python -b |
| # Copyright 1999-2014 Gentoo Foundation |
| # Distributed under the terms of the GNU General Public License v2 |
| |
| import codecs |
| import io |
| import os |
| import re |
| import sys |
| |
| here_doc_re = re.compile(r'.*\s<<[-]?(\w+)$') |
| func_start_re = re.compile(r'^[-\w]+\s*\(\)\s*$') |
| func_end_re = re.compile(r'^\}$') |
| |
| var_assign_re = re.compile(r'(^|^declare\s+-\S+\s+|^declare\s+|^export\s+)([^=\s]+)=("|\')?.*$') |
| close_quote_re = re.compile(r'(\\"|"|\')\s*$') |
| readonly_re = re.compile(r'^declare\s+-(\S*)r(\S*)\s+') |
| # declare without assignment |
| var_declare_re = re.compile(r'^declare(\s+-\S+)?\s+([^=\s]+)\s*$') |
| |
| def have_end_quote(quote, line): |
| """ |
| Check if the line has an end quote (useful for handling multi-line |
| quotes). This handles escaped double quotes that may occur at the |
| end of a line. The posix spec does not allow escaping of single |
| quotes inside of single quotes, so that case is not handled. |
| """ |
| close_quote_match = close_quote_re.search(line) |
| return close_quote_match is not None and \ |
| close_quote_match.group(1) == quote |
| |
| def filter_declare_readonly_opt(line): |
| readonly_match = readonly_re.match(line) |
| if readonly_match is not None: |
| declare_opts = '' |
| for i in (1, 2): |
| group = readonly_match.group(i) |
| if group is not None: |
| declare_opts += group |
| if declare_opts: |
| line = 'declare -%s %s' % \ |
| (declare_opts, line[readonly_match.end():]) |
| else: |
| line = 'declare ' + line[readonly_match.end():] |
| return line |
| |
| def filter_bash_environment(pattern, file_in, file_out): |
| # Filter out any instances of the \1 character from variable values |
| # since this character multiplies each time that the environment |
| # is saved (strange bash behavior). This can eventually result in |
| # mysterious 'Argument list too long' errors from programs that have |
| # huge strings of \1 characters in their environment. See bug #222091. |
| here_doc_delim = None |
| in_func = None |
| multi_line_quote = None |
| multi_line_quote_filter = None |
| for line in file_in: |
| if multi_line_quote is not None: |
| if not multi_line_quote_filter: |
| file_out.write(line.replace("\1", "")) |
| if have_end_quote(multi_line_quote, line): |
| multi_line_quote = None |
| multi_line_quote_filter = None |
| continue |
| if here_doc_delim is None and in_func is None: |
| var_assign_match = var_assign_re.match(line) |
| if var_assign_match is not None: |
| quote = var_assign_match.group(3) |
| filter_this = pattern.match(var_assign_match.group(2)) \ |
| is not None |
| # Exclude the start quote when searching for the end quote, |
| # to ensure that the start quote is not misidentified as the |
| # end quote (happens if there is a newline immediately after |
| # the start quote). |
| if quote is not None and not \ |
| have_end_quote(quote, line[var_assign_match.end(2)+2:]): |
| multi_line_quote = quote |
| multi_line_quote_filter = filter_this |
| if not filter_this: |
| line = filter_declare_readonly_opt(line) |
| file_out.write(line.replace("\1", "")) |
| continue |
| else: |
| declare_match = var_declare_re.match(line) |
| if declare_match is not None: |
| # declare without assignment |
| filter_this = pattern.match(declare_match.group(2)) \ |
| is not None |
| if not filter_this: |
| line = filter_declare_readonly_opt(line) |
| file_out.write(line) |
| continue |
| |
| if here_doc_delim is not None: |
| if here_doc_delim.match(line): |
| here_doc_delim = None |
| file_out.write(line) |
| continue |
| here_doc = here_doc_re.match(line) |
| if here_doc is not None: |
| here_doc_delim = re.compile("^%s$" % here_doc.group(1)) |
| file_out.write(line) |
| continue |
| # Note: here-documents are handled before functions since otherwise |
| # it would be possible for the content of a here-document to be |
| # mistaken as the end of a function. |
| if in_func: |
| if func_end_re.match(line) is not None: |
| in_func = None |
| file_out.write(line) |
| continue |
| in_func = func_start_re.match(line) |
| if in_func is not None: |
| file_out.write(line) |
| continue |
| # This line is not recognized as part of a variable assignment, |
| # function definition, or here document, so just allow it to |
| # pass through. |
| file_out.write(line) |
| |
| if __name__ == "__main__": |
| description = "Filter out variable assignments for variable " + \ |
| "names matching a given PATTERN " + \ |
| "while leaving bash function definitions and here-documents " + \ |
| "intact. The PATTERN is a space separated list of variable names" + \ |
| " and it supports python regular expression syntax." |
| usage = "usage: %s PATTERN" % os.path.basename(sys.argv[0]) |
| args = sys.argv[1:] |
| |
| if '-h' in args or '--help' in args: |
| sys.stdout.write(usage + "\n") |
| sys.stdout.flush() |
| sys.exit(os.EX_OK) |
| |
| if len(args) != 1: |
| sys.stderr.write(usage + "\n") |
| sys.stderr.write("Exactly one PATTERN argument required.\n") |
| sys.stderr.flush() |
| sys.exit(2) |
| |
| file_in = sys.stdin |
| file_out = sys.stdout |
| if sys.hexversion >= 0x3000000: |
| file_in = codecs.iterdecode(sys.stdin.buffer.raw, |
| 'utf_8', errors='replace') |
| file_out = io.TextIOWrapper(sys.stdout.buffer, |
| 'utf_8', errors='backslashreplace') |
| |
| var_pattern = args[0].split() |
| |
| # Filter invalid variable names that are not supported by bash. |
| var_pattern.append(r'\d.*') |
| var_pattern.append(r'.*\W.*') |
| |
| var_pattern = "^(%s)$" % "|".join(var_pattern) |
| filter_bash_environment( |
| re.compile(var_pattern), file_in, file_out) |
| file_out.flush() |