Browse Source

lint: Remove checkbashisms.pl, assume it is in PATH.

ng0 4 years ago
parent
commit
7f5ae7f617
3 changed files with 2 additions and 902 deletions
  1. 2 20
      lint/Makefile.am
  2. 0 68
      lint/checkbashisms.1
  3. 0 814
      lint/checkbashisms.pl.in

+ 2 - 20
lint/Makefile.am

@@ -1,25 +1,10 @@
 all: check-linters
 
-do_subst = $(SED) -e 's,[@]PERL[@],$(PERL),g'
-
-SUFFIXES = pl.in .pl
-
-checkbashisms.pl: checkbashisms.pl.in Makefile
-	$(do_subst) < $(srcdir)/checkbashisms.pl.in > checkbashisms.pl
-	chmod +x checkbashisms.pl
-
-CLEANFILES=	\
-		checkbashisms.pl
-
-noinst_SCRIPTS =		\
-		$(CLEANFILES)
-
 # Check for bashisms in shell scripts
 # Very verbose, need to exclude more files.
 check-bashism:
-	printf "Run checkbashism on all .sh files.\n"
-	printf "Currently this expects checkbashism.pl at a fixed location."
-	find '..' -type f ! -path '*/.*' ! -path '*/_*' -name '*.sh' -print0 | xargs -0 $(srcdir)/checkbashisms.pl -f 2>&1 | tee $(srcdir)/bashism.log || true
+	printf "If checkbashisms.pl is in PATH, run checkbashism on all .sh files.\n"
+	find '..' -type f ! -path '*/.*' ! -path '*/_*' -name '*.sh' -print0 | xargs -0 checkbashisms.pl -f 2>&1 | tee $(srcdir)/bashism.log || true
 
 check-python:
 	printf "Running flake8 and 2to3 if detected.\n"
@@ -57,6 +42,3 @@ check-texinfo:
 	@cd $(top_srcdir)/doc/tutorial ; find . -type f ! -path '*/.*' -name '*.texi' -print0 | xargs -0 awk '/XXX/ {print FILENAME":"NR":"$$0}' >> $(srcdir)/texinfo_tutorial.log || true
 
 check-linters: check-bashism check-python check-man check-texinfo
-
-EXTRA_DIST = \
-  checkbashisms.pl.in

+ 0 - 68
lint/checkbashisms.1

@@ -1,68 +0,0 @@
-.TH CHECKBASHISMS 1 "Debian Utilities" "DEBIAN" \" -*- nroff -*-
-.SH NAME
-checkbashisms \- check for bashisms in /bin/sh scripts
-.SH SYNOPSIS
-\fBcheckbashisms\fR \fIscript\fR ...
-.br
-\fBcheckbashisms \-\-help\fR|\fB\-\-version\fR
-.SH DESCRIPTION
-\fBcheckbashisms\fR, based on one of the checks from the \fBlintian\fR
-system, performs basic checks on \fI/bin/sh\fR shell scripts for the
-possible presence of bashisms.  It takes the names of the shell
-scripts on the command line, and outputs warnings if possible bashisms
-are detected.
-.PP
-Note that the definition of a bashism in this context roughly equates
-to "a shell feature that is not required to be supported by POSIX"; this
-means that some issues flagged may be permitted under optional sections
-of POSIX, such as XSI or User Portability.
-.PP
-In cases where POSIX and Debian Policy disagree, \fBcheckbashisms\fR by
-default allows extensions permitted by Policy but may also provide
-options for stricter checking.
-.SH OPTIONS
-.TP
-.BR \-\-help ", " \-h
-Show a summary of options.
-.TP
-.BR \-\-newline ", " \-n
-Check for "\fBecho \-n\fR" usage (non POSIX but required by Debian Policy 10.4.)
-.TP
-.BR \-\-posix ", " \-p
-Check for issues which are non POSIX but required to be supported by Debian
-Policy 10.4 (implies \fB\-n\fR).
-.TP
-.BR \-\-force ", " \-f
-Force each script to be checked, even if it would normally not be (for
-instance, it has a bash or non POSIX shell shebang or appears to be a
-shell wrapper).
-.TP
-.BR \-\-extra ", " \-x
-Highlight lines which, whilst they do not contain bashisms, may be
-useful in determining whether a particular issue is a false positive
-which may be ignored.
-For example, the use of "\fB$BASH_ENV\fR" may be preceded by checking
-whether "\fB$BASH\fR" is set.
-.TP
-.BR \-\-version ", " \-v
-Show version and copyright information.
-.SH "EXIT VALUES"
-The exit value will be 0 if no possible bashisms or other problems
-were detected.  Otherwise it will be the sum of the following error
-values:
-.TP
-1
-A possible bashism was detected.
-.TP
-2
-A file was skipped for some reason, for example, because it was
-unreadable or not found.  The warning message will give details.
-.TP
-4
-No bashisms were detected in a bash script.
-.SH "SEE ALSO"
-.BR lintian (1)
-.SH AUTHOR
-\fBcheckbashisms\fR was originally written as a shell script by Yann Dirson
-<\fIdirson@debian.org\fR> and rewritten in Perl with many more features by
-Julian Gilbey <\fIjdg@debian.org\fR>.

+ 0 - 814
lint/checkbashisms.pl.in

@@ -1,814 +0,0 @@
-#!@PERL@
-
-# This script is essentially copied from /usr/share/lintian/checks/scripts,
-# which is:
-#   Copyright (C) 1998 Richard Braakman
-#   Copyright (C) 2002 Josip Rodin
-# This version is
-#   Copyright (C) 2003 Julian Gilbey
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <https://www.gnu.org/licenses/>.
-
-use strict;
-use warnings;
-use Getopt::Long qw(:config bundling permute no_getopt_compat);
-use File::Temp qw/tempfile/;
-
-sub init_hashes;
-
-(my $progname = $0) =~ s|.*/||;
-
-my $usage = <<"EOF";
-Usage: $progname [-n] [-f] [-x] script ...
-   or: $progname --help
-   or: $progname --version
-This script performs basic checks for the presence of bashisms
-in /bin/sh scripts and the lack of bashisms in /bin/bash ones.
-EOF
-
-my $version = <<"EOF";
-This is $progname, from the Debian devscripts package, version ###VERSION###
-This code is copyright 2003 by Julian Gilbey <jdg\@debian.org>,
-based on original code which is copyright 1998 by Richard Braakman
-and copyright 2002 by Josip Rodin.
-This program comes with ABSOLUTELY NO WARRANTY.
-You are free to redistribute this code under the terms of the
-GNU General Public License, version 2, or (at your option) any later version.
-EOF
-
-my ($opt_echo, $opt_force, $opt_extra, $opt_posix);
-my ($opt_help, $opt_version);
-my @filenames;
-
-# Detect if STDIN is a pipe
-if (scalar(@ARGV) == 0 && (-p STDIN or -f STDIN)) {
-    push(@ARGV, '-');
-}
-
-##
-## handle command-line options
-##
-$opt_help = 1 if int(@ARGV) == 0;
-
-GetOptions(
-    "help|h"    => \$opt_help,
-    "version|v" => \$opt_version,
-    "newline|n" => \$opt_echo,
-    "force|f"   => \$opt_force,
-    "extra|x"   => \$opt_extra,
-    "posix|p"   => \$opt_posix,
-  )
-  or die
-"Usage: $progname [options] filelist\nRun $progname --help for more details\n";
-
-if ($opt_help)    { print $usage;   exit 0; }
-if ($opt_version) { print $version; exit 0; }
-
-$opt_echo = 1 if $opt_posix;
-
-my $mode     = 0;
-my $issues   = 0;
-my $status   = 0;
-my $makefile = 0;
-my (%bashisms, %string_bashisms, %singlequote_bashisms);
-
-my $LEADIN
-  = qr'(?:(?:^|[`&;(|{])\s*|(?:(?:if|elif|while)(?:\s+!)?|then|do|shell)\s+)';
-init_hashes;
-
-my @bashisms_keys             = sort keys %bashisms;
-my @string_bashisms_keys      = sort keys %string_bashisms;
-my @singlequote_bashisms_keys = sort keys %singlequote_bashisms;
-
-foreach my $filename (@ARGV) {
-    my $check_lines_count = -1;
-
-    my $display_filename = $filename;
-
-    if ($filename eq '-') {
-        my $tmp_fh;
-        ($tmp_fh, $filename)
-          = tempfile("chkbashisms_tmp.XXXX", TMPDIR => 1, UNLINK => 1);
-        while (my $line = <STDIN>) {
-            print $tmp_fh $line;
-        }
-        close($tmp_fh);
-        $display_filename = "(stdin)";
-    }
-
-    if (!$opt_force) {
-        $check_lines_count = script_is_evil_and_wrong($filename);
-    }
-
-    if ($check_lines_count == 0 or $check_lines_count == 1) {
-        warn
-"script $display_filename does not appear to be a /bin/sh script; skipping\n";
-        next;
-    }
-
-    if ($check_lines_count != -1) {
-        warn
-"script $display_filename appears to be a shell wrapper; only checking the first "
-          . "$check_lines_count lines\n";
-    }
-
-    unless (open C, '<', $filename) {
-        warn "cannot open script $display_filename for reading: $!\n";
-        $status |= 2;
-        next;
-    }
-
-    $issues = 0;
-    $mode   = 0;
-    my $cat_string         = "";
-    my $cat_indented       = 0;
-    my $quote_string       = "";
-    my $last_continued     = 0;
-    my $continued          = 0;
-    my $found_rules        = 0;
-    my $buffered_orig_line = "";
-    my $buffered_line      = "";
-    my %start_lines;
-
-    while (<C>) {
-        next unless ($check_lines_count == -1 or $. <= $check_lines_count);
-
-        if ($. == 1) {    # This should be an interpreter line
-            if (m,^\#!\s*(?:\S+/env\s+)?(\S+),) {
-                my $interpreter = $1;
-
-                if ($interpreter =~ m,(?:^|/)make$,) {
-                    init_hashes if !$makefile++;
-                    $makefile = 1;
-                } else {
-                    init_hashes if $makefile--;
-                    $makefile = 0;
-                }
-                next if $opt_force;
-
-                if ($interpreter =~ m,(?:^|/)bash$,) {
-                    $mode = 1;
-                } elsif ($interpreter !~ m,(?:^|/)(sh|dash|posh)$,) {
-### ksh/zsh?
-                    warn
-"script $display_filename does not appear to be a /bin/sh script; skipping\n";
-                    $status |= 2;
-                    last;
-                }
-            } else {
-                warn
-"script $display_filename does not appear to have a \#! interpreter line;\nyou may get strange results\n";
-            }
-        }
-
-        chomp;
-        my $orig_line = $_;
-
-        # We want to remove end-of-line comments, so need to skip
-        # comments that appear inside balanced pairs
-        # of single or double quotes
-
-        # Remove comments in the "quoted" part of a line that starts
-        # in a quoted block? The problem is that we have no idea
-        # whether the program interpreting the block treats the
-        # quote character as part of the comment or as a quote
-        # terminator. We err on the side of caution and assume it
-        # will be treated as part of the comment.
-        # s/^(?:.*?[^\\])?$quote_string(.*)$/$1/ if $quote_string ne "";
-
-        # skip comment lines
-        if (   m,^\s*\#,
-            && $quote_string eq ''
-            && $buffered_line eq ''
-            && $cat_string eq '') {
-            next;
-        }
-
-        # Remove quoted strings so we can more easily ignore comments
-        # inside them
-        s/(^|[^\\](?:\\\\)*)\'(?:\\.|[^\\\'])+\'/$1''/g;
-        s/(^|[^\\](?:\\\\)*)\"(?:\\.|[^\\\"])+\"/$1""/g;
-
-        # If inside a quoted string, remove everything before the quote
-        s/^.+?\'//
-          if ($quote_string eq "'");
-        s/^.+?[^\\]\"//
-          if ($quote_string eq '"');
-
-        # If the remaining string contains what looks like a comment,
-        # eat it. In either case, swap the unmodified script line
-        # back in for processing.
-        if (m/(?:^|[^[\\])[\s\&;\(\)](\#.*$)/) {
-            $_ = $orig_line;
-            s/\Q$1\E//;    # eat comments
-        } else {
-            $_ = $orig_line;
-        }
-
-        # Handle line continuation
-        if (!$makefile && $cat_string eq '' && m/\\$/) {
-            chop;
-            $buffered_line .= $_;
-            $buffered_orig_line .= $orig_line . "\n";
-            next;
-        }
-
-        if ($buffered_line ne '') {
-            $_                  = $buffered_line . $_;
-            $orig_line          = $buffered_orig_line . $orig_line;
-            $buffered_line      = '';
-            $buffered_orig_line = '';
-        }
-
-        if ($makefile) {
-            $last_continued = $continued;
-            if (/[^\\]\\$/) {
-                $continued = 1;
-            } else {
-                $continued = 0;
-            }
-
-            # Don't match lines that look like a rule if we're in a
-            # continuation line before the start of the rules
-            if (/^[\w%-]+:+\s.*?;?(.*)$/
-                and !($last_continued and !$found_rules)) {
-                $found_rules = 1;
-                $_ = $1 if $1;
-            }
-
-            last
-              if m%^\s*(override\s|export\s)?\s*SHELL\s*:?=\s*(/bin/)?bash\s*%;
-
-            # Remove "simple" target names
-            s/^[\w%.-]+(?:\s+[\w%.-]+)*::?//;
-            s/^\t//;
-            s/(?<!\$)\$\((\w+)\)/\${$1}/g;
-            s/(\$){2}/$1/g;
-            s/^[\s\t]*[@-]{1,2}//;
-        }
-
-        if (
-            $cat_string ne ""
-            && (m/^\Q$cat_string\E$/
-                || ($cat_indented && m/^\t*\Q$cat_string\E$/))
-        ) {
-            $cat_string = "";
-            next;
-        }
-        my $within_another_shell = 0;
-        if (m,(^|\s+)((/usr)?/bin/)?((b|d)?a|k|z|t?c)sh\s+-c\s*.+,) {
-            $within_another_shell = 1;
-        }
-        # if cat_string is set, we are in a HERE document and need not
-        # check for things
-        if ($cat_string eq "" and !$within_another_shell) {
-            my $found       = 0;
-            my $match       = '';
-            my $explanation = '';
-            my $line        = $_;
-
-            # Remove "" / '' as they clearly aren't quoted strings
-            # and not considering them makes the matching easier
-            $line =~ s/(^|[^\\])(\'\')+/$1/g;
-            $line =~ s/(^|[^\\])(\"\")+/$1/g;
-
-            if ($quote_string ne "") {
-                my $otherquote = ($quote_string eq "\"" ? "\'" : "\"");
-                # Inside a quoted block
-                if ($line =~ /(?:^|^.*?[^\\])$quote_string(.*)$/) {
-                    my $rest     = $1;
-                    my $templine = $line;
-
-                    # Remove quoted strings delimited with $otherquote
-                    $templine
-                      =~ s/(^|[^\\])$otherquote[^$quote_string]*?[^\\]$otherquote/$1/g;
-                    # Remove quotes that are themselves quoted
-                    # "a'b"
-                    $templine
-                      =~ s/(^|[^\\])$otherquote.*?$quote_string.*?[^\\]$otherquote/$1/g;
-                    # "\""
-                    $templine
-                      =~ s/(^|[^\\])$quote_string\\$quote_string$quote_string/$1/g;
-
-                    # After all that, were there still any quotes left?
-                    my $count = () = $templine =~ /(^|[^\\])$quote_string/g;
-                    next if $count == 0;
-
-                    $count = () = $rest =~ /(^|[^\\])$quote_string/g;
-                    if ($count % 2 == 0) {
-                        # Quoted block ends on this line
-                        # Ignore everything before the closing quote
-                        $line = $rest || '';
-                        $quote_string = "";
-                    } else {
-                        next;
-                    }
-                } else {
-                    # Still inside the quoted block, skip this line
-                    next;
-                }
-            }
-
-            # Check even if we removed the end of a quoted block
-            # in the previous check, as a single line can end one
-            # block and begin another
-            if ($quote_string eq "") {
-                # Possible start of a quoted block
-                for my $quote ("\"", "\'") {
-                    my $templine = $line;
-                    my $otherquote = ($quote eq "\"" ? "\'" : "\"");
-
-                    # Remove balanced quotes and their content
-                    while (1) {
-                        my ($length_single, $length_double) = (0, 0);
-
-                        # Determine which one would match first:
-                        if ($templine
-                            =~ m/(^.+?(?:^|[^\\\"](?:\\\\)*)\')[^\']*\'/) {
-                            $length_single = length($1);
-                        }
-                        if ($templine
-                            =~ m/(^.*?(?:^|[^\\\'](?:\\\\)*)\")(?:\\.|[^\\\"])+\"/
-                        ) {
-                            $length_double = length($1);
-                        }
-
-                        # Now simplify accordingly (shorter is preferred):
-                        if (
-                            $length_single != 0
-                            && (   $length_single < $length_double
-                                || $length_double == 0)
-                        ) {
-                            $templine =~ s/(^|[^\\\"](?:\\\\)*)\'[^\']*\'/$1/;
-                        } elsif ($length_double != 0) {
-                            $templine
-                              =~ s/(^|[^\\\'](?:\\\\)*)\"(?:\\.|[^\\\"])+\"/$1/;
-                        } else {
-                            last;
-                        }
-                    }
-
-                    # Don't flag quotes that are themselves quoted
-                    # "a'b"
-                    $templine =~ s/$otherquote.*?$quote.*?$otherquote//g;
-                    # "\""
-                    $templine =~ s/(^|[^\\])$quote\\$quote$quote/$1/g;
-                    # \' or \"
-                    $templine =~ s/\\[\'\"]//g;
-                    my $count = () = $templine =~ /(^|(?!\\))$quote/g;
-
-                    # If there's an odd number of non-escaped
-                    # quotes in the line it's almost certainly the
-                    # start of a quoted block.
-                    if ($count % 2 == 1) {
-                        $quote_string = $quote;
-                        $start_lines{'quote_string'} = $.;
-                        $line =~ s/^(.*)$quote.*$/$1/;
-                        last;
-                    }
-                }
-            }
-
-            # since this test is ugly, I have to do it by itself
-            # detect source (.) trying to pass args to the command it runs
-            # The first expression weeds out '. "foo bar"'
-            if (    not $found
-                and not
-m/$LEADIN\.\s+(\"[^\"]+\"|\'[^\']+\'|\$\([^)]+\)+(?:\/[^\s;]+)?)\s*(\&|\||\d?>|<|;|\Z)/o
-                and m/$LEADIN(\.\s+[^\s;\`:]+\s+([^\s;]+))/o) {
-                if ($2 =~ /^(\&|\||\d?>|<)/) {
-                    # everything is ok
-                    ;
-                } else {
-                    $found       = 1;
-                    $match       = $1;
-                    $explanation = "sourced script with arguments";
-                    output_explanation($display_filename, $orig_line,
-                        $explanation);
-                }
-            }
-
-            # Remove "quoted quotes". They're likely to be inside
-            # another pair of quotes; we're not interested in
-            # them for their own sake and removing them makes finding
-            # the limits of the outer pair far easier.
-            $line =~ s/(^|[^\\\'\"])\"\'\"/$1/g;
-            $line =~ s/(^|[^\\\'\"])\'\"\'/$1/g;
-
-            foreach my $re (@singlequote_bashisms_keys) {
-                my $expl = $singlequote_bashisms{$re};
-                if ($line =~ m/($re)/) {
-                    $found       = 1;
-                    $match       = $1;
-                    $explanation = $expl;
-                    output_explanation($display_filename, $orig_line,
-                        $explanation);
-                }
-            }
-
-            my $re = '(?<![\$\\\])\$\'[^\']+\'';
-            if ($line =~ m/(.*)($re)/o) {
-                my $count = () = $1 =~ /(^|[^\\])\'/g;
-                if ($count % 2 == 0) {
-                    output_explanation($display_filename, $orig_line,
-                        q<$'...' should be "$(printf '...')">);
-                }
-            }
-
-            # $cat_line contains the version of the line we'll check
-            # for heredoc delimiters later. Initially, remove any
-            # spaces between << and the delimiter to make the following
-            # updates to $cat_line easier. However, don't remove the
-            # spaces if the delimiter starts with a -, as that changes
-            # how the delimiter is searched.
-            my $cat_line = $line;
-            $cat_line =~ s/(<\<-?)\s+(?!-)/$1/g;
-
-            # Ignore anything inside single quotes; it could be an
-            # argument to grep or the like.
-            $line =~ s/(^|[^\\\"](?:\\\\)*)\'(?:\\.|[^\\\'])+\'/$1''/g;
-
-            # As above, with the exception that we don't remove the string
-            # if the quote is immediately preceded by a < or a -, so we
-            # can match "foo <<-?'xyz'" as a heredoc later
-            # The check is a little more greedy than we'd like, but the
-            # heredoc test itself will weed out any false positives
-            $cat_line =~ s/(^|[^<\\\"-](?:\\\\)*)\'(?:\\.|[^\\\'])+\'/$1''/g;
-
-            $re = '(?<![\$\\\])\$\"[^\"]+\"';
-            if ($line =~ m/(.*)($re)/o) {
-                my $count = () = $1 =~ /(^|[^\\])\"/g;
-                if ($count % 2 == 0) {
-                    output_explanation($display_filename, $orig_line,
-                        q<$"foo" should be eval_gettext "foo">);
-                }
-            }
-
-            foreach my $re (@string_bashisms_keys) {
-                my $expl = $string_bashisms{$re};
-                if ($line =~ m/($re)/) {
-                    $found       = 1;
-                    $match       = $1;
-                    $explanation = $expl;
-                    output_explanation($display_filename, $orig_line,
-                        $explanation);
-                }
-            }
-
-            # We've checked for all the things we still want to notice in
-            # double-quoted strings, so now remove those strings as well.
-            $line =~ s/(^|[^\\\'](?:\\\\)*)\"(?:\\.|[^\\\"])+\"/$1""/g;
-            $cat_line =~ s/(^|[^<\\\'-](?:\\\\)*)\"(?:\\.|[^\\\"])+\"/$1""/g;
-            foreach my $re (@bashisms_keys) {
-                my $expl = $bashisms{$re};
-                if ($line =~ m/($re)/) {
-                    $found       = 1;
-                    $match       = $1;
-                    $explanation = $expl;
-                    output_explanation($display_filename, $orig_line,
-                        $explanation);
-                }
-            }
-            # This check requires the value to be compared, which could
-            # be done in the regex itself but requires "use re 'eval'".
-            # So it's better done in its own
-            if ($line =~ m/$LEADIN((?:exit|return)\s+(\d{3,}))/o && $2 > 255) {
-                $explanation = 'exit|return status code greater than 255';
-                output_explanation($display_filename, $orig_line,
-                    $explanation);
-            }
-
-            # Only look for the beginning of a heredoc here, after we've
-            # stripped out quoted material, to avoid false positives.
-            if ($cat_line
-                =~ m/(?:^|[^<])\<\<(\-?)\s*(?:(?!<|'|")((?:[^\s;>|]+(?:(?<=\\)[\s;>|])?)+)|[\'\"](.*?)[\'\"])/
-            ) {
-                $cat_indented = ($1 && $1 eq '-') ? 1 : 0;
-                my $quoted = defined($3);
-                $cat_string = $quoted ? $3 : $2;
-                unless ($quoted) {
-                    # Now strip backslashes. Keep the position of the
-                    # last match in a variable, as s/// resets it back
-                    # to undef, but we don't want that.
-                    my $pos = 0;
-                    pos($cat_string) = $pos;
-                    while ($cat_string =~ s/\G(.*?)\\/$1/) {
-                        # position += length of match + the character
-                        # that followed the backslash:
-                        $pos += length($1) + 1;
-                        pos($cat_string) = $pos;
-                    }
-                }
-                $start_lines{'cat_string'} = $.;
-            }
-        }
-    }
-
-    warn
-"error: $display_filename:  Unterminated heredoc found, EOF reached. Wanted: <$cat_string>, opened in line $start_lines{'cat_string'}\n"
-      if ($cat_string ne '');
-    warn
-"error: $display_filename: Unterminated quoted string found, EOF reached. Wanted: <$quote_string>, opened in line $start_lines{'quote_string'}\n"
-      if ($quote_string ne '');
-    warn "error: $display_filename: EOF reached while on line continuation.\n"
-      if ($buffered_line ne '');
-
-    close C;
-
-    if ($mode && !$issues) {
-        warn "could not find any possible bashisms in bash script $filename\n";
-        $status |= 4;
-    }
-}
-
-exit $status;
-
-sub output_explanation {
-    my ($filename, $line, $explanation) = @_;
-
-    if ($mode) {
-        # When examining a bash script, just flag that there are indeed
-        # bashisms present
-        $issues = 1;
-    } else {
-        warn "possible bashism in $filename line $. ($explanation):\n$line\n";
-        $status |= 1;
-    }
-}
-
-# Returns non-zero if the given file is not actually a shell script,
-# just looks like one.
-sub script_is_evil_and_wrong {
-    my ($filename) = @_;
-    my $ret = -1;
-    # lintian's version of this function aborts if the file
-    # can't be opened, but we simply return as the next
-    # test in the calling code handles reporting the error
-    # itself
-    open(IN, '<', $filename) or return $ret;
-    my $i            = 0;
-    my $var          = "0";
-    my $backgrounded = 0;
-    local $_;
-    while (<IN>) {
-        chomp;
-        next if /^#/o;
-        next if /^$/o;
-        last if (++$i > 55);
-        if (
-            m~
-	    # the exec should either be "eval"ed or a new statement
-	    (^\s*|\beval\s*[\'\"]|(;|&&|\b(then|else))\s*)
-
-	    # eat anything between the exec and $0
-	    exec\s*.+\s*
-
-	    # optionally quoted executable name (via $0)
-	    .?\$$var.?\s*
-
-	    # optional "end of options" indicator
-	    (--\s*)?
-
-	    # Match expressions of the form '${1+$@}', '${1:+"$@"',
-	    # '"${1+$@', "$@", etc where the quotes (before the dollar
-	    # sign(s)) are optional and the second (or only if the $1
-	    # clause is omitted) parameter may be $@ or $*.
-	    #
-	    # Finally the whole subexpression may be omitted for scripts
-	    # which do not pass on their parameters (i.e. after re-execing
-	    # they take their parameters (and potentially data) from stdin
-	    .?(\$\{1:?\+.?)?(\$(\@|\*))?~x
-        ) {
-            $ret = $. - 1;
-            last;
-        } elsif (/^\s*(\w+)=\$0;/) {
-            $var = $1;
-        } elsif (
-            m~
-	    # Match scripts which use "foo $0 $@ &\nexec true\n"
-	    # Program name
-	    \S+\s+
-
-	    # As above
-	    .?\$$var.?\s*
-	    (--\s*)?
-	    .?(\$\{1:?\+.?)?(\$(\@|\*))?.?\s*\&~x
-        ) {
-
-            $backgrounded = 1;
-        } elsif (
-            $backgrounded
-            and m~
-	    # the exec should either be "eval"ed or a new statement
-	    (^\s*|\beval\s*[\'\"]|(;|&&|\b(then|else))\s*)
-	    exec\s+true(\s|\Z)~x
-        ) {
-
-            $ret = $. - 1;
-            last;
-        } elsif (m~\@DPATCH\@~) {
-            $ret = $. - 1;
-            last;
-        }
-
-    }
-    close IN;
-    return $ret;
-}
-
-sub init_hashes {
-
-    %bashisms = (
-        qr'(?:^|\s+)function [^<>\(\)\[\]\{\};|\s]+(\s|\(|\Z)' =>
-          q<'function' is useless>,
-        $LEADIN . qr'select\s+\w+'               => q<'select' is not POSIX>,
-        qr'(test|-o|-a)\s*[^\s]+\s+==\s'         => q<should be 'b = a'>,
-        qr'\[\s+[^\]]+\s+==\s'                   => q<should be 'b = a'>,
-        qr'\s\|\&'                               => q<pipelining is not POSIX>,
-        qr'[^\\\$]\{([^\s\\\}]*?,)+[^\\\}\s]*\}' => q<brace expansion>,
-        qr'\{\d+\.\.\d+(?:\.\.\d+)?\}' =>
-          q<brace expansion, {a..b[..c]}should be $(seq a [c] b)>,
-        qr'(?i)\{[a-z]\.\.[a-z](?:\.\.\d+)?\}' => q<brace expansion>,
-        qr'(?:^|\s+)\w+\[\d+\]='               => q<bash arrays, H[0]>,
-        $LEADIN
-          . qr'read\s+(?:-[a-qs-zA-Z\d-]+)' =>
-          q<read with option other than -r>,
-        $LEADIN
-          . qr'read\s*(?:-\w+\s*)*(?:\".*?\"|[\'].*?[\'])?\s*(?:;|$)' =>
-          q<read without variable>,
-        $LEADIN . qr'echo\s+(-n\s+)?-n?en?\s' => q<echo -e>,
-        $LEADIN . qr'exec\s+-[acl]'           => q<exec -c/-l/-a name>,
-        $LEADIN . qr'let\s'                   => q<let ...>,
-        qr'(?<![\$\(])\(\(.*\)\)'             => q<'((' should be '$(('>,
-        qr'(?:^|\s+)(\[|test)\s+-a' => q<test with unary -a (should be -e)>,
-        qr'\&>'                     => q<should be \>word 2\>&1>,
-        qr'(<\&|>\&)\s*((-|\d+)[^\s;|)}`&\\\\]|[^-\d\s]+(?<!\$)(?!\d))' =>
-          q<should be \>word 2\>&1>,
-        qr'\[\[(?!:)' =>
-          q<alternative test command ([[ foo ]] should be [ foo ])>,
-        qr'/dev/(tcp|udp)'               => q</dev/(tcp|udp)>,
-        $LEADIN . qr'builtin\s'          => q<builtin>,
-        $LEADIN . qr'caller\s'           => q<caller>,
-        $LEADIN . qr'compgen\s'          => q<compgen>,
-        $LEADIN . qr'complete\s'         => q<complete>,
-        $LEADIN . qr'declare\s'          => q<declare>,
-        $LEADIN . qr'dirs(\s|\Z)'        => q<dirs>,
-        $LEADIN . qr'disown\s'           => q<disown>,
-        $LEADIN . qr'enable\s'           => q<enable>,
-        $LEADIN . qr'mapfile\s'          => q<mapfile>,
-        $LEADIN . qr'readarray\s'        => q<readarray>,
-        $LEADIN . qr'shopt(\s|\Z)'       => q<shopt>,
-        $LEADIN . qr'suspend\s'          => q<suspend>,
-        $LEADIN . qr'time\s'             => q<time>,
-        $LEADIN . qr'type\s'             => q<type>,
-        $LEADIN . qr'typeset\s'          => q<typeset>,
-        $LEADIN . qr'ulimit(\s|\Z)'      => q<ulimit>,
-        $LEADIN . qr'set\s+-[BHT]+'      => q<set -[BHT]>,
-        $LEADIN . qr'alias\s+-p'         => q<alias -p>,
-        $LEADIN . qr'unalias\s+-a'       => q<unalias -a>,
-        $LEADIN . qr'local\s+-[a-zA-Z]+' => q<local -opt>,
-        # function '=' is special-cased due to bash arrays (think of "foo=()")
-        qr'(?:^|\s)\s*=\s*\(\s*\)\s*([\{|\(]|\Z)' =>
-          q<function names should only contain [a-z0-9_]>,
-qr'(?:^|\s)(?<func>function\s)?\s*(?:[^<>\(\)\[\]\{\};|\s]*[^<>\(\)\[\]\{\};|\s\w][^<>\(\)\[\]\{\};|\s]*)(?(<func>)(?=)|(?<!=))\s*(?(<func>)(?:\(\s*\))?|\(\s*\))\s*([\{|\(]|\Z)'
-          => q<function names should only contain [a-z0-9_]>,
-        $LEADIN . qr'(push|pop)d(\s|\Z)' => q<(push|pop)d>,
-        $LEADIN . qr'export\s+-[^p]'   => q<export only takes -p as an option>,
-        qr'(?:^|\s+)[<>]\(.*?\)'       => q<\<() process substitution>,
-        $LEADIN . qr'readonly\s+-[af]' => q<readonly -[af]>,
-        $LEADIN . qr'(sh|\$\{?SHELL\}?) -[rD]' => q<sh -[rD]>,
-        $LEADIN . qr'(sh|\$\{?SHELL\}?) --\w+' => q<sh --long-option>,
-        $LEADIN . qr'(sh|\$\{?SHELL\}?) [-+]O' => q<sh [-+]O>,
-        qr'\[\^[^]]+\]'                        => q<[^] should be [!]>,
-        $LEADIN
-          . qr'printf\s+-v' =>
-          q<'printf -v var ...' should be var='$(printf ...)'>,
-        $LEADIN . qr'coproc\s' => q<coproc>,
-        qr';;?&'               => q<;;& and ;& special case operators>,
-        $LEADIN . qr'jobs\s'   => q<jobs>,
- #	$LEADIN . qr'jobs\s+-[^lp]\s' =>  q<'jobs' with option other than -l or -p>,
-        $LEADIN
-          . qr'command\s+-[^p]\s' => q<'command' with option other than -p>,
-        $LEADIN
-          . qr'setvar\s' =>
-          q<setvar 'foo' 'bar' should be eval 'foo="'"$bar"'"'>,
-        $LEADIN
-          . qr'trap\s+["\']?.*["\']?\s+.*(?:ERR|DEBUG|RETURN)' =>
-          q<trap with ERR|DEBUG|RETURN>,
-        $LEADIN
-          . qr'(?:exit|return)\s+-\d' =>
-          q<exit|return with negative status code>,
-        $LEADIN
-          . qr'(?:exit|return)\s+--' =>
-          q<'exit --' should be 'exit' (idem for return)>,
-        $LEADIN
-          . qr'sleep\s+(?:-|\d+(?:[.a-z]|\s+\d))' =>
-          q<sleep only takes one integer>,
-        $LEADIN . qr'hash(\s|\Z)' => q<hash>,
-        qr'(?:[:=\s])~(?:[+-]|[+-]?\d+)(?:[/\s]|\Z)' =>
-          q<non-standard tilde expansion>,
-    );
-
-    %string_bashisms = (
-        qr'\$\[[^][]+\]' => q<'$[' should be '$(('>,
-        qr'\$\{(?:\w+|@|\*)\:(?:\d+|\$\{?\w+\}?)+(?::(?:\d+|\$\{?\w+\}?)+)?\}'
-          => q<${foo:3[:1]}>,
-        qr'\$\{!\w+[\@*]\}' => q<${!prefix[*|@]>,
-        qr'\$\{!\w+\}'      => q<${!name}>,
-        qr'\$\{(?:\w+|@|\*)([,^]{1,2}.*?)\}' =>
-          q<${parm,[,][pat]} or ${parm^[^][pat]}>,
-        qr'\$\{[@*]([#%]{1,2}.*?)\}' => q<${[@|*]#[#]pat} or ${[@|*]%[%]pat}>,
-        qr'\$\{#[@*]\}'              => q<${#@} or ${#*}>,
-        qr'\$\{(?:\w+|@|\*)(/.+?){1,2}\}' => q<${parm/?/pat[/str]}>,
-        qr'\$\{\#?\w+\[.+\](?:[/,:#%^].+?)?\}' =>
-          q<bash arrays, ${name[0|*|@]}>,
-        qr'\$\{?RANDOM\}?\b'          => q<$RANDOM>,
-        qr'\$\{?(OS|MACH)TYPE\}?\b'   => q<$(OS|MACH)TYPE>,
-        qr'\$\{?HOST(TYPE|NAME)\}?\b' => q<$HOST(TYPE|NAME)>,
-        qr'\$\{?DIRSTACK\}?\b'        => q<$DIRSTACK>,
-        qr'\$\{?EUID\}?\b'            => q<$EUID should be "$(id -u)">,
-        qr'\$\{?UID\}?\b'             => q<$UID should be "$(id -ru)">,
-        qr'\$\{?SECONDS\}?\b'         => q<$SECONDS>,
-        qr'\$\{?BASH_[A-Z]+\}?\b'     => q<$BASH_SOMETHING>,
-        qr'\$\{?SHELLOPTS\}?\b'       => q<$SHELLOPTS>,
-        qr'\$\{?PIPESTATUS\}?\b'      => q<$PIPESTATUS>,
-        qr'\$\{?SHLVL\}?\b'           => q<$SHLVL>,
-        qr'\$\{?FUNCNAME\}?\b'        => q<$FUNCNAME>,
-        qr'\$\{?TMOUT\}?\b'           => q<$TMOUT>,
-        qr'(?:^|\s+)TMOUT='           => q<TMOUT=>,
-        qr'\$\{?TIMEFORMAT\}?\b'      => q<$TIMEFORMAT>,
-        qr'(?:^|\s+)TIMEFORMAT='      => q<TIMEFORMAT=>,
-        qr'(?<![$\\])\$\{?_\}?\b'     => q<$_>,
-        qr'(?:^|\s+)GLOBIGNORE='      => q<GLOBIGNORE=>,
-        qr'<<<'                       => q<\<\<\< here string>,
-        $LEADIN
-          . qr'echo\s+(?:-[^e\s]+\s+)?\"[^\"]*(\\[abcEfnrtv0])+.*?[\"]' =>
-          q<unsafe echo with backslash>,
-        qr'\$\(\([\s\w$*/+-]*\w\+\+.*?\)\)' =>
-          q<'$((n++))' should be '$n; $((n=n+1))'>,
-        qr'\$\(\([\s\w$*/+-]*\+\+\w.*?\)\)' =>
-          q<'$((++n))' should be '$((n=n+1))'>,
-        qr'\$\(\([\s\w$*/+-]*\w\-\-.*?\)\)' =>
-          q<'$((n--))' should be '$n; $((n=n-1))'>,
-        qr'\$\(\([\s\w$*/+-]*\-\-\w.*?\)\)' =>
-          q<'$((--n))' should be '$((n=n-1))'>,
-        qr'\$\(\([\s\w$*/+-]*\*\*.*?\)\)' => q<exponentiation is not POSIX>,
-        $LEADIN . qr'printf\s["\'][^"\']*?%q.+?["\']' => q<printf %q>,
-    );
-
-    %singlequote_bashisms = (
-        $LEADIN
-          . qr'echo\s+(?:-[^e\s]+\s+)?\'[^\']*(\\[abcEfnrtv0])+.*?[\']' =>
-          q<unsafe echo with backslash>,
-        $LEADIN
-          . qr'source\s+[\"\']?(?:\.\/|\/|\$|[\w~.-])\S*' =>
-          q<should be '.', not 'source'>,
-    );
-
-    if ($opt_echo) {
-        $bashisms{ $LEADIN . qr'echo\s+-[A-Za-z]*n' } = q<echo -n>;
-    }
-    if ($opt_posix) {
-        $bashisms{ $LEADIN . qr'local\s+\w+(\s+\W|\s*[;&|)]|$)' }
-          = q<local foo>;
-        $bashisms{ $LEADIN . qr'local\s+\w+=' }      = q<local foo=bar>;
-        $bashisms{ $LEADIN . qr'local\s+\w+\s+\w+' } = q<local x y>;
-        $bashisms{ $LEADIN . qr'((?:test|\[)\s+.+\s-[ao])\s' } = q<test -a/-o>;
-        $bashisms{ $LEADIN . qr'kill\s+-[^sl]\w*' } = q<kill -[0-9] or -[A-Z]>;
-        $bashisms{ $LEADIN . qr'trap\s+["\']?.*["\']?\s+.*[1-9]' }
-          = q<trap with signal numbers>;
-    }
-
-    if ($makefile) {
-        $string_bashisms{qr'(\$\(|\`)\s*\<\s*([^\s\)]{2,}|[^DF])\s*(\)|\`)'}
-          = q<'$(\< foo)' should be '$(cat foo)'>;
-    } else {
-        $bashisms{ $LEADIN . qr'\w+\+=' } = q<should be VAR="${VAR}foo">;
-        $string_bashisms{qr'(\$\(|\`)\s*\<\s*\S+\s*(\)|\`)'}
-          = q<'$(\< foo)' should be '$(cat foo)'>;
-    }
-
-    if ($opt_extra) {
-        $string_bashisms{qr'\$\{?BASH\}?\b'}            = q<$BASH>;
-        $string_bashisms{qr'(?:^|\s+)RANDOM='}          = q<RANDOM=>;
-        $string_bashisms{qr'(?:^|\s+)(OS|MACH)TYPE='}   = q<(OS|MACH)TYPE=>;
-        $string_bashisms{qr'(?:^|\s+)HOST(TYPE|NAME)='} = q<HOST(TYPE|NAME)=>;
-        $string_bashisms{qr'(?:^|\s+)DIRSTACK='}        = q<DIRSTACK=>;
-        $string_bashisms{qr'(?:^|\s+)EUID='}            = q<EUID=>;
-        $string_bashisms{qr'(?:^|\s+)UID='}             = q<UID=>;
-        $string_bashisms{qr'(?:^|\s+)BASH(_[A-Z]+)?='}  = q<BASH(_SOMETHING)=>;
-        $string_bashisms{qr'(?:^|\s+)SHELLOPTS='}       = q<SHELLOPTS=>;
-        $string_bashisms{qr'\$\{?POSIXLY_CORRECT\}?\b'} = q<$POSIXLY_CORRECT>;
-    }
-}