| #!/usr/bin/python -b |
| # Copyright 1999-2014 Gentoo Foundation |
| # Distributed under the terms of the GNU General Public License v2 |
| |
| import os |
| import re |
| import sys |
| |
| here_doc_re = re.compile(br".*\s<<[-]?(\w+)$") |
| func_start_re = re.compile(br"^[-\w]+\s*\(\)\s*$") |
| func_end_re = re.compile(br"^\}$") |
| |
| var_assign_re = re.compile( |
| br'(^|^declare\s+-\S+\s+|^declare\s+|^export\s+)([^=\s]+)=("|\')?.*$' |
| ) |
| close_quote_re = re.compile(br'(\\"|"|\')\s*$') |
| readonly_re = re.compile(br"^declare\s+-(\S*)r(\S*)\s+") |
| # declare without assignment |
| var_declare_re = re.compile(br"^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 = b"" |
| for i in (1, 2): |
| group = readonly_match.group(i) |
| if group is not None: |
| declare_opts += group |
| if declare_opts: |
| line = b"declare -" + declare_opts + b" " + line[readonly_match.end() :] |
| else: |
| line = b"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(b"\1", b"")) |
| 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(b"\1", b"")) |
| 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(b"^" + here_doc.group(1) + b"$") |
| 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.buffer |
| file_out = sys.stdout.buffer |
| var_pattern = os.fsencode(args[0]).split() |
| |
| # Filter invalid variable names that are not supported by bash. |
| var_pattern.append(br"\d.*") |
| var_pattern.append(br".*\W.*") |
| |
| var_pattern = b"^(" + b"|".join(var_pattern) + b")$" |
| filter_bash_environment(re.compile(var_pattern), file_in, file_out) |
| file_out.flush() |