packages: checkbashisms/checkbashisms.1 (NEW), checkbashisms/checkbashisms....

glen glen at pld-linux.org
Thu May 13 19:06:13 CEST 2010


Author: glen                         Date: Thu May 13 17:06:13 2010 GMT
Module: packages                      Tag: HEAD
---- Log message:
- adopted from altlinux src.rpm

---- Files affected:
packages/checkbashisms:
   checkbashisms.1 (NONE -> 1.1)  (NEW), checkbashisms.pl (NONE -> 1.1)  (NEW)

---- Diffs:

================================================================
Index: packages/checkbashisms/checkbashisms.1
diff -u /dev/null packages/checkbashisms/checkbashisms.1:1.1
--- /dev/null	Thu May 13 19:06:13 2010
+++ packages/checkbashisms/checkbashisms.1	Thu May 13 19:06:07 2010
@@ -0,0 +1,65 @@
+.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|\-\-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 "echo \-n" 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 \-n).
+.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 "$BASH_ENV" may be preceeded by checking
+whether "$BASH" 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.
+.SH "SEE ALSO"
+.BR lintian (1).
+.SH AUTHOR
+\fBcheckbashisms\fR was originally written as a shell script by Yann Dirson
+<dirson at debian.org> and rewritten in Perl with many more features by
+Julian Gilbey <jdg at debian.org>.

================================================================
Index: packages/checkbashisms/checkbashisms.pl
diff -u /dev/null packages/checkbashisms/checkbashisms.pl:1.1
--- /dev/null	Thu May 13 19:06:13 2010
+++ packages/checkbashisms/checkbashisms.pl	Thu May 13 19:06:07 2010
@@ -0,0 +1,579 @@
+#! /usr/bin/perl -w
+
+# 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, you can find it on the World Wide
+# Web at http://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+# MA 02111-1307, USA.
+
+use strict;
+use Getopt::Long;
+
+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.
+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);
+
+##
+## 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 $status = 0;
+my $makefile = 0;
+my (%bashisms, %string_bashisms, %singlequote_bashisms);
+
+my $LEADIN = qr'(?:(?:^|[`&;(|{])\s*|(?:if|then|do|while|shell)\s+)';
+init_hashes;
+
+foreach my $filename (@ARGV) {
+    my $check_lines_count = -1;
+
+    if (!$opt_force) {
+	$check_lines_count = script_is_evil_and_wrong($filename);
+    }
+
+    if ($check_lines_count == 0 or $check_lines_count == 1) {
+	warn "script $filename does not appear to be a /bin/sh script; skipping\n";
+	next;
+    }
+
+    if ($check_lines_count != -1) {
+	warn "script $filename appears to be a shell wrapper; only checking the first "
+	     . "$check_lines_count lines\n";
+    }
+
+    unless (open C, '<', $filename) {
+	warn "cannot open script $filename for reading: $!\n";
+	$status |= 2;
+	next;
+    }
+
+    my $cat_string = "";
+    my $quote_string = "";
+    my $last_continued = 0;
+    my $continued = 0;
+    my $found_rules = 0;
+
+    while (<C>) {
+	next unless ($check_lines_count == -1 or $. <= $check_lines_count);
+
+	if ($. == 1) { # This should be an interpreter line
+	    if (m,^\#!\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$,) {
+		    warn "script $filename is already a bash script; skipping\n";
+		    $status |= 2;
+		    last;  # end this file
+		}
+		elsif ($interpreter !~ m,/(sh|posh)$,) {
+### ksh/zsh?
+		    warn "script $filename does not appear to be a /bin/sh script; skipping\n";
+		    $status |= 2;
+		    last;
+		}
+	    } else {
+		warn "script $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 "";
+
+	next if m,^\s*\#,;  # skip comment lines
+
+	# Remove quoted strings so we can more easily ignore comments
+	# inside them
+	s/(^|[^\\](?:\\\\)*)\'(?:\\.|[^\\\'])+\'/$1''/g;
+	s/(^|[^\\](?:\\\\)*)\"(?:\\.|[^\\\"])+\"/$1""/g;
+
+	# 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;
+	}
+
+	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%^(export )?SHELL\s*:?=\s*(/bin/)?bash\s*%;
+
+	    s/^\t//;
+	    s/(\$){2}/$1/;
+	    s/^[\s\t]*@//;
+	}
+
+	if ($cat_string ne "" and m/^\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
+		    $templine =~ s/(^|[^\\\"](?:\\\\)*)\'(?:\\.|[^\\\'])+\'/$1/g;
+		    $templine =~ s/(^|[^\\\'](?:\\\\)*)\"(?:\\.|[^\\\"])+\"/$1/g;
+
+		    # Don't flag quotes that are themselves quoted
+		    # "a'b"
+		    $templine =~ s/$otherquote.*?$quote.*?$otherquote//g;
+		    # "\""
+		    $templine =~ s/(^|[^\\])$quote\\$quote$quote/$1/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;
+			$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)/
+		and m/$LEADIN(\.\s+[^\s;\`:]+\s+([^\s;]+))/) {
+		if ($2 =~ /^(\&|\||\d?>|<)/) {
+		    # everything is ok
+		    ;
+		} else {
+		    $found = 1;
+		    $match = $1;
+		    $explanation = "sourced script with arguments";
+		    output_explanation($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;
+
+	    while (my ($re,$expl) = each %singlequote_bashisms) {
+		if ($line =~ m/($re)/) {
+		    $found = 1;
+		    $match = $1;
+		    $explanation = $expl;
+		    output_explanation($filename, $orig_line, $explanation);
+		}
+	    }
+
+	    my $re='(?<![\$\\\])\$\'[^\']+\'';
+	    if ($line =~ m/(.*)($re)/){
+		my $count = () = $1 =~ /(^|[^\\])\'/g;
+		if( $count % 2 == 0 ) {
+		    output_explanation($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.
+	    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 preceeded 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)/){
+		my $count = () = $1 =~ /(^|[^\\])\"/g;
+		if( $count % 2 == 0 ) {
+		    output_explanation($filename, $orig_line, q<$"foo" should be eval_gettext "foo">);
+		}
+	    }   
+
+	    while (my ($re,$expl) = each %string_bashisms) {
+		if ($line =~ m/($re)/) {
+		    $found = 1;
+		    $match = $1;
+		    $explanation = $expl;
+		    output_explanation($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;
+	    while (my ($re,$expl) = each %bashisms) {
+	        if ($line =~ m/($re)/) {
+		    $found = 1;
+		    $match = $1;
+		    $explanation = $expl;
+		    output_explanation($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*(?:[\\]?(\w+)|[\'\"](.*?)[\'\"])/) {
+		$cat_string = $1;
+		$cat_string = $2 if not defined $cat_string;
+            }
+	}
+    }
+
+    close C;
+}
+
+exit $status;
+
+sub output_explanation {
+    my ($filename, $line, $explanation) = @_;
+
+    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 \w+(\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+\}' =>          q<brace expansion, should be $(seq a b)>,
+	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]+)' =>
+				       q<should be \>word 2\>&1>,
+	$LEADIN . qr'kill\s+-[^sl]\w*' => q<kill -[0-9] or -[A-Z]>,
+	$LEADIN . qr'trap\s+["\']?.*["\']?\s+.*[1-9]' => q<trap with signal numbers>,
+	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>,
+	qr'(?:^|\s+)\s*\(?\w*[^\(\w\s]+\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 substituion>,
+	$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>,
+    );
+
<<Diff was trimmed, longer than 597 lines>>


More information about the pld-cvs-commit mailing list