xref: /aosp_15_r20/external/ltp/scripts/checkbashisms.pl (revision 49cdfc7efb34551c7342be41a7384b9c40d7cab7)
1*49cdfc7eSAndroid Build Coastguard Worker#!/usr/bin/perl
2*49cdfc7eSAndroid Build Coastguard Worker
3*49cdfc7eSAndroid Build Coastguard Worker# This script is essentially copied from /usr/share/lintian/checks/scripts,
4*49cdfc7eSAndroid Build Coastguard Worker# which is:
5*49cdfc7eSAndroid Build Coastguard Worker#   Copyright (C) 1998 Richard Braakman
6*49cdfc7eSAndroid Build Coastguard Worker#   Copyright (C) 2002 Josip Rodin
7*49cdfc7eSAndroid Build Coastguard Worker# This version is
8*49cdfc7eSAndroid Build Coastguard Worker#   Copyright (C) 2003 Julian Gilbey
9*49cdfc7eSAndroid Build Coastguard Worker#
10*49cdfc7eSAndroid Build Coastguard Worker# This program is free software; you can redistribute it and/or modify
11*49cdfc7eSAndroid Build Coastguard Worker# it under the terms of the GNU General Public License as published by
12*49cdfc7eSAndroid Build Coastguard Worker# the Free Software Foundation; either version 2 of the License, or
13*49cdfc7eSAndroid Build Coastguard Worker# (at your option) any later version.
14*49cdfc7eSAndroid Build Coastguard Worker#
15*49cdfc7eSAndroid Build Coastguard Worker# This program is distributed in the hope that it will be useful,
16*49cdfc7eSAndroid Build Coastguard Worker# but WITHOUT ANY WARRANTY; without even the implied warranty of
17*49cdfc7eSAndroid Build Coastguard Worker# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18*49cdfc7eSAndroid Build Coastguard Worker# GNU General Public License for more details.
19*49cdfc7eSAndroid Build Coastguard Worker#
20*49cdfc7eSAndroid Build Coastguard Worker# You should have received a copy of the GNU General Public License
21*49cdfc7eSAndroid Build Coastguard Worker# along with this program. If not, see <https://www.gnu.org/licenses/>.
22*49cdfc7eSAndroid Build Coastguard Worker
23*49cdfc7eSAndroid Build Coastguard Workeruse strict;
24*49cdfc7eSAndroid Build Coastguard Workeruse warnings;
25*49cdfc7eSAndroid Build Coastguard Workeruse Getopt::Long qw(:config bundling permute no_getopt_compat);
26*49cdfc7eSAndroid Build Coastguard Workeruse File::Temp qw/tempfile/;
27*49cdfc7eSAndroid Build Coastguard Worker
28*49cdfc7eSAndroid Build Coastguard Workersub init_hashes;
29*49cdfc7eSAndroid Build Coastguard Worker
30*49cdfc7eSAndroid Build Coastguard Worker(my $progname = $0) =~ s|.*/||;
31*49cdfc7eSAndroid Build Coastguard Worker
32*49cdfc7eSAndroid Build Coastguard Workermy $usage = <<"EOF";
33*49cdfc7eSAndroid Build Coastguard WorkerUsage: $progname [-n] [-f] [-x] [-e] script ...
34*49cdfc7eSAndroid Build Coastguard Worker   or: $progname --help
35*49cdfc7eSAndroid Build Coastguard Worker   or: $progname --version
36*49cdfc7eSAndroid Build Coastguard WorkerThis script performs basic checks for the presence of bashisms
37*49cdfc7eSAndroid Build Coastguard Workerin /bin/sh scripts and the lack of bashisms in /bin/bash ones.
38*49cdfc7eSAndroid Build Coastguard WorkerEOF
39*49cdfc7eSAndroid Build Coastguard Worker
40*49cdfc7eSAndroid Build Coastguard Workermy $version = <<"EOF";
41*49cdfc7eSAndroid Build Coastguard WorkerThis is $progname, from the Debian devscripts package, version 2.20.5
42*49cdfc7eSAndroid Build Coastguard WorkerThis code is copyright 2003 by Julian Gilbey <jdg\@debian.org>,
43*49cdfc7eSAndroid Build Coastguard Workerbased on original code which is copyright 1998 by Richard Braakman
44*49cdfc7eSAndroid Build Coastguard Workerand copyright 2002 by Josip Rodin.
45*49cdfc7eSAndroid Build Coastguard WorkerThis program comes with ABSOLUTELY NO WARRANTY.
46*49cdfc7eSAndroid Build Coastguard WorkerYou are free to redistribute this code under the terms of the
47*49cdfc7eSAndroid Build Coastguard WorkerGNU General Public License, version 2, or (at your option) any later version.
48*49cdfc7eSAndroid Build Coastguard WorkerEOF
49*49cdfc7eSAndroid Build Coastguard Worker
50*49cdfc7eSAndroid Build Coastguard Workermy ($opt_echo, $opt_force, $opt_extra, $opt_posix, $opt_early_fail);
51*49cdfc7eSAndroid Build Coastguard Workermy ($opt_help, $opt_version);
52*49cdfc7eSAndroid Build Coastguard Workermy @filenames;
53*49cdfc7eSAndroid Build Coastguard Worker
54*49cdfc7eSAndroid Build Coastguard Worker# Detect if STDIN is a pipe
55*49cdfc7eSAndroid Build Coastguard Workerif (scalar(@ARGV) == 0 && (-p STDIN or -f STDIN)) {
56*49cdfc7eSAndroid Build Coastguard Worker    push(@ARGV, '-');
57*49cdfc7eSAndroid Build Coastguard Worker}
58*49cdfc7eSAndroid Build Coastguard Worker
59*49cdfc7eSAndroid Build Coastguard Worker##
60*49cdfc7eSAndroid Build Coastguard Worker## handle command-line options
61*49cdfc7eSAndroid Build Coastguard Worker##
62*49cdfc7eSAndroid Build Coastguard Worker$opt_help = 1 if int(@ARGV) == 0;
63*49cdfc7eSAndroid Build Coastguard Worker
64*49cdfc7eSAndroid Build Coastguard WorkerGetOptions(
65*49cdfc7eSAndroid Build Coastguard Worker    "help|h"       => \$opt_help,
66*49cdfc7eSAndroid Build Coastguard Worker    "version|v"    => \$opt_version,
67*49cdfc7eSAndroid Build Coastguard Worker    "newline|n"    => \$opt_echo,
68*49cdfc7eSAndroid Build Coastguard Worker    "force|f"      => \$opt_force,
69*49cdfc7eSAndroid Build Coastguard Worker    "extra|x"      => \$opt_extra,
70*49cdfc7eSAndroid Build Coastguard Worker    "posix|p"      => \$opt_posix,
71*49cdfc7eSAndroid Build Coastguard Worker    "early-fail|e" => \$opt_early_fail,
72*49cdfc7eSAndroid Build Coastguard Worker  )
73*49cdfc7eSAndroid Build Coastguard Worker  or die
74*49cdfc7eSAndroid Build Coastguard Worker"Usage: $progname [options] filelist\nRun $progname --help for more details\n";
75*49cdfc7eSAndroid Build Coastguard Worker
76*49cdfc7eSAndroid Build Coastguard Workerif ($opt_help)    { print $usage;   exit 0; }
77*49cdfc7eSAndroid Build Coastguard Workerif ($opt_version) { print $version; exit 0; }
78*49cdfc7eSAndroid Build Coastguard Worker
79*49cdfc7eSAndroid Build Coastguard Worker$opt_echo = 1 if $opt_posix;
80*49cdfc7eSAndroid Build Coastguard Worker
81*49cdfc7eSAndroid Build Coastguard Workermy $mode     = 0;
82*49cdfc7eSAndroid Build Coastguard Workermy $issues   = 0;
83*49cdfc7eSAndroid Build Coastguard Workermy $status   = 0;
84*49cdfc7eSAndroid Build Coastguard Workermy $makefile = 0;
85*49cdfc7eSAndroid Build Coastguard Workermy (%bashisms, %string_bashisms, %singlequote_bashisms);
86*49cdfc7eSAndroid Build Coastguard Worker
87*49cdfc7eSAndroid Build Coastguard Workermy $LEADIN
88*49cdfc7eSAndroid Build Coastguard Worker  = qr'(?:(?:^|[`&;(|{])\s*|(?:(?:if|elif|while)(?:\s+!)?|then|do|shell)\s+)';
89*49cdfc7eSAndroid Build Coastguard Workerinit_hashes;
90*49cdfc7eSAndroid Build Coastguard Worker
91*49cdfc7eSAndroid Build Coastguard Workermy @bashisms_keys             = sort keys %bashisms;
92*49cdfc7eSAndroid Build Coastguard Workermy @string_bashisms_keys      = sort keys %string_bashisms;
93*49cdfc7eSAndroid Build Coastguard Workermy @singlequote_bashisms_keys = sort keys %singlequote_bashisms;
94*49cdfc7eSAndroid Build Coastguard Worker
95*49cdfc7eSAndroid Build Coastguard Workerforeach my $filename (@ARGV) {
96*49cdfc7eSAndroid Build Coastguard Worker    my $check_lines_count = -1;
97*49cdfc7eSAndroid Build Coastguard Worker
98*49cdfc7eSAndroid Build Coastguard Worker    my $display_filename = $filename;
99*49cdfc7eSAndroid Build Coastguard Worker
100*49cdfc7eSAndroid Build Coastguard Worker    if ($filename eq '-') {
101*49cdfc7eSAndroid Build Coastguard Worker        my $tmp_fh;
102*49cdfc7eSAndroid Build Coastguard Worker        ($tmp_fh, $filename)
103*49cdfc7eSAndroid Build Coastguard Worker          = tempfile("chkbashisms_tmp.XXXX", TMPDIR => 1, UNLINK => 1);
104*49cdfc7eSAndroid Build Coastguard Worker        while (my $line = <STDIN>) {
105*49cdfc7eSAndroid Build Coastguard Worker            print $tmp_fh $line;
106*49cdfc7eSAndroid Build Coastguard Worker        }
107*49cdfc7eSAndroid Build Coastguard Worker        close($tmp_fh);
108*49cdfc7eSAndroid Build Coastguard Worker        $display_filename = "(stdin)";
109*49cdfc7eSAndroid Build Coastguard Worker    }
110*49cdfc7eSAndroid Build Coastguard Worker
111*49cdfc7eSAndroid Build Coastguard Worker    if (!$opt_force) {
112*49cdfc7eSAndroid Build Coastguard Worker        $check_lines_count = script_is_evil_and_wrong($filename);
113*49cdfc7eSAndroid Build Coastguard Worker    }
114*49cdfc7eSAndroid Build Coastguard Worker
115*49cdfc7eSAndroid Build Coastguard Worker    if ($check_lines_count == 0 or $check_lines_count == 1) {
116*49cdfc7eSAndroid Build Coastguard Worker        warn
117*49cdfc7eSAndroid Build Coastguard Worker"script $display_filename does not appear to be a /bin/sh script; skipping\n";
118*49cdfc7eSAndroid Build Coastguard Worker        next;
119*49cdfc7eSAndroid Build Coastguard Worker    }
120*49cdfc7eSAndroid Build Coastguard Worker
121*49cdfc7eSAndroid Build Coastguard Worker    if ($check_lines_count != -1) {
122*49cdfc7eSAndroid Build Coastguard Worker        warn
123*49cdfc7eSAndroid Build Coastguard Worker"script $display_filename appears to be a shell wrapper; only checking the first "
124*49cdfc7eSAndroid Build Coastguard Worker          . "$check_lines_count lines\n";
125*49cdfc7eSAndroid Build Coastguard Worker    }
126*49cdfc7eSAndroid Build Coastguard Worker
127*49cdfc7eSAndroid Build Coastguard Worker    unless (open C, '<', $filename) {
128*49cdfc7eSAndroid Build Coastguard Worker        warn "cannot open script $display_filename for reading: $!\n";
129*49cdfc7eSAndroid Build Coastguard Worker        $status |= 2;
130*49cdfc7eSAndroid Build Coastguard Worker        next;
131*49cdfc7eSAndroid Build Coastguard Worker    }
132*49cdfc7eSAndroid Build Coastguard Worker
133*49cdfc7eSAndroid Build Coastguard Worker    $issues = 0;
134*49cdfc7eSAndroid Build Coastguard Worker    $mode   = 0;
135*49cdfc7eSAndroid Build Coastguard Worker    my $cat_string         = "";
136*49cdfc7eSAndroid Build Coastguard Worker    my $cat_indented       = 0;
137*49cdfc7eSAndroid Build Coastguard Worker    my $quote_string       = "";
138*49cdfc7eSAndroid Build Coastguard Worker    my $last_continued     = 0;
139*49cdfc7eSAndroid Build Coastguard Worker    my $continued          = 0;
140*49cdfc7eSAndroid Build Coastguard Worker    my $found_rules        = 0;
141*49cdfc7eSAndroid Build Coastguard Worker    my $buffered_orig_line = "";
142*49cdfc7eSAndroid Build Coastguard Worker    my $buffered_line      = "";
143*49cdfc7eSAndroid Build Coastguard Worker    my %start_lines;
144*49cdfc7eSAndroid Build Coastguard Worker
145*49cdfc7eSAndroid Build Coastguard Worker    while (<C>) {
146*49cdfc7eSAndroid Build Coastguard Worker        next unless ($check_lines_count == -1 or $. <= $check_lines_count);
147*49cdfc7eSAndroid Build Coastguard Worker
148*49cdfc7eSAndroid Build Coastguard Worker        if ($. == 1) {    # This should be an interpreter line
149*49cdfc7eSAndroid Build Coastguard Worker            if (m,^\#!\s*(?:\S+/env\s+)?(\S+),) {
150*49cdfc7eSAndroid Build Coastguard Worker                my $interpreter = $1;
151*49cdfc7eSAndroid Build Coastguard Worker
152*49cdfc7eSAndroid Build Coastguard Worker                if ($interpreter =~ m,(?:^|/)make$,) {
153*49cdfc7eSAndroid Build Coastguard Worker                    init_hashes if !$makefile++;
154*49cdfc7eSAndroid Build Coastguard Worker                    $makefile = 1;
155*49cdfc7eSAndroid Build Coastguard Worker                } else {
156*49cdfc7eSAndroid Build Coastguard Worker                    init_hashes if $makefile--;
157*49cdfc7eSAndroid Build Coastguard Worker                    $makefile = 0;
158*49cdfc7eSAndroid Build Coastguard Worker                }
159*49cdfc7eSAndroid Build Coastguard Worker                next if $opt_force;
160*49cdfc7eSAndroid Build Coastguard Worker
161*49cdfc7eSAndroid Build Coastguard Worker                if ($interpreter =~ m,(?:^|/)bash$,) {
162*49cdfc7eSAndroid Build Coastguard Worker                    $mode = 1;
163*49cdfc7eSAndroid Build Coastguard Worker                } elsif ($interpreter !~ m,(?:^|/)(sh|dash|posh)$,) {
164*49cdfc7eSAndroid Build Coastguard Worker### ksh/zsh?
165*49cdfc7eSAndroid Build Coastguard Worker                    warn
166*49cdfc7eSAndroid Build Coastguard Worker"script $display_filename does not appear to be a /bin/sh script; skipping\n";
167*49cdfc7eSAndroid Build Coastguard Worker                    $status |= 2;
168*49cdfc7eSAndroid Build Coastguard Worker                    last;
169*49cdfc7eSAndroid Build Coastguard Worker                }
170*49cdfc7eSAndroid Build Coastguard Worker            } else {
171*49cdfc7eSAndroid Build Coastguard Worker                warn
172*49cdfc7eSAndroid Build Coastguard Worker"script $display_filename does not appear to have a \#! interpreter line;\nyou may get strange results\n";
173*49cdfc7eSAndroid Build Coastguard Worker            }
174*49cdfc7eSAndroid Build Coastguard Worker        }
175*49cdfc7eSAndroid Build Coastguard Worker
176*49cdfc7eSAndroid Build Coastguard Worker        chomp;
177*49cdfc7eSAndroid Build Coastguard Worker        my $orig_line = $_;
178*49cdfc7eSAndroid Build Coastguard Worker
179*49cdfc7eSAndroid Build Coastguard Worker        # We want to remove end-of-line comments, so need to skip
180*49cdfc7eSAndroid Build Coastguard Worker        # comments that appear inside balanced pairs
181*49cdfc7eSAndroid Build Coastguard Worker        # of single or double quotes
182*49cdfc7eSAndroid Build Coastguard Worker
183*49cdfc7eSAndroid Build Coastguard Worker        # Remove comments in the "quoted" part of a line that starts
184*49cdfc7eSAndroid Build Coastguard Worker        # in a quoted block? The problem is that we have no idea
185*49cdfc7eSAndroid Build Coastguard Worker        # whether the program interpreting the block treats the
186*49cdfc7eSAndroid Build Coastguard Worker        # quote character as part of the comment or as a quote
187*49cdfc7eSAndroid Build Coastguard Worker        # terminator. We err on the side of caution and assume it
188*49cdfc7eSAndroid Build Coastguard Worker        # will be treated as part of the comment.
189*49cdfc7eSAndroid Build Coastguard Worker        # s/^(?:.*?[^\\])?$quote_string(.*)$/$1/ if $quote_string ne "";
190*49cdfc7eSAndroid Build Coastguard Worker
191*49cdfc7eSAndroid Build Coastguard Worker        # skip comment lines
192*49cdfc7eSAndroid Build Coastguard Worker        if (   m,^\s*\#,
193*49cdfc7eSAndroid Build Coastguard Worker            && $quote_string eq ''
194*49cdfc7eSAndroid Build Coastguard Worker            && $buffered_line eq ''
195*49cdfc7eSAndroid Build Coastguard Worker            && $cat_string eq '') {
196*49cdfc7eSAndroid Build Coastguard Worker            next;
197*49cdfc7eSAndroid Build Coastguard Worker        }
198*49cdfc7eSAndroid Build Coastguard Worker
199*49cdfc7eSAndroid Build Coastguard Worker        # Remove quoted strings so we can more easily ignore comments
200*49cdfc7eSAndroid Build Coastguard Worker        # inside them
201*49cdfc7eSAndroid Build Coastguard Worker        s/(^|[^\\](?:\\\\)*)\'(?:\\.|[^\\\'])+\'/$1''/g;
202*49cdfc7eSAndroid Build Coastguard Worker        s/(^|[^\\](?:\\\\)*)\"(?:\\.|[^\\\"])+\"/$1""/g;
203*49cdfc7eSAndroid Build Coastguard Worker
204*49cdfc7eSAndroid Build Coastguard Worker        # If inside a quoted string, remove everything before the quote
205*49cdfc7eSAndroid Build Coastguard Worker        s/^.+?\'//
206*49cdfc7eSAndroid Build Coastguard Worker          if ($quote_string eq "'");
207*49cdfc7eSAndroid Build Coastguard Worker        s/^.+?[^\\]\"//
208*49cdfc7eSAndroid Build Coastguard Worker          if ($quote_string eq '"');
209*49cdfc7eSAndroid Build Coastguard Worker
210*49cdfc7eSAndroid Build Coastguard Worker        # If the remaining string contains what looks like a comment,
211*49cdfc7eSAndroid Build Coastguard Worker        # eat it. In either case, swap the unmodified script line
212*49cdfc7eSAndroid Build Coastguard Worker        # back in for processing.
213*49cdfc7eSAndroid Build Coastguard Worker        if (m/(?:^|[^[\\])[\s\&;\(\)](\#.*$)/) {
214*49cdfc7eSAndroid Build Coastguard Worker            $_ = $orig_line;
215*49cdfc7eSAndroid Build Coastguard Worker            s/\Q$1\E//;    # eat comments
216*49cdfc7eSAndroid Build Coastguard Worker        } else {
217*49cdfc7eSAndroid Build Coastguard Worker            $_ = $orig_line;
218*49cdfc7eSAndroid Build Coastguard Worker        }
219*49cdfc7eSAndroid Build Coastguard Worker
220*49cdfc7eSAndroid Build Coastguard Worker        # Handle line continuation
221*49cdfc7eSAndroid Build Coastguard Worker        if (!$makefile && $cat_string eq '' && m/\\$/) {
222*49cdfc7eSAndroid Build Coastguard Worker            chop;
223*49cdfc7eSAndroid Build Coastguard Worker            $buffered_line      .= $_;
224*49cdfc7eSAndroid Build Coastguard Worker            $buffered_orig_line .= $orig_line . "\n";
225*49cdfc7eSAndroid Build Coastguard Worker            next;
226*49cdfc7eSAndroid Build Coastguard Worker        }
227*49cdfc7eSAndroid Build Coastguard Worker
228*49cdfc7eSAndroid Build Coastguard Worker        if ($buffered_line ne '') {
229*49cdfc7eSAndroid Build Coastguard Worker            $_                  = $buffered_line . $_;
230*49cdfc7eSAndroid Build Coastguard Worker            $orig_line          = $buffered_orig_line . $orig_line;
231*49cdfc7eSAndroid Build Coastguard Worker            $buffered_line      = '';
232*49cdfc7eSAndroid Build Coastguard Worker            $buffered_orig_line = '';
233*49cdfc7eSAndroid Build Coastguard Worker        }
234*49cdfc7eSAndroid Build Coastguard Worker
235*49cdfc7eSAndroid Build Coastguard Worker        if ($makefile) {
236*49cdfc7eSAndroid Build Coastguard Worker            $last_continued = $continued;
237*49cdfc7eSAndroid Build Coastguard Worker            if (/[^\\]\\$/) {
238*49cdfc7eSAndroid Build Coastguard Worker                $continued = 1;
239*49cdfc7eSAndroid Build Coastguard Worker            } else {
240*49cdfc7eSAndroid Build Coastguard Worker                $continued = 0;
241*49cdfc7eSAndroid Build Coastguard Worker            }
242*49cdfc7eSAndroid Build Coastguard Worker
243*49cdfc7eSAndroid Build Coastguard Worker            # Don't match lines that look like a rule if we're in a
244*49cdfc7eSAndroid Build Coastguard Worker            # continuation line before the start of the rules
245*49cdfc7eSAndroid Build Coastguard Worker            if (/^[\w%-]+:+\s.*?;?(.*)$/
246*49cdfc7eSAndroid Build Coastguard Worker                and !($last_continued and !$found_rules)) {
247*49cdfc7eSAndroid Build Coastguard Worker                $found_rules = 1;
248*49cdfc7eSAndroid Build Coastguard Worker                $_           = $1 if $1;
249*49cdfc7eSAndroid Build Coastguard Worker            }
250*49cdfc7eSAndroid Build Coastguard Worker
251*49cdfc7eSAndroid Build Coastguard Worker            last
252*49cdfc7eSAndroid Build Coastguard Worker              if m%^\s*(override\s|export\s)?\s*SHELL\s*:?=\s*(/bin/)?bash\s*%;
253*49cdfc7eSAndroid Build Coastguard Worker
254*49cdfc7eSAndroid Build Coastguard Worker            # Remove "simple" target names
255*49cdfc7eSAndroid Build Coastguard Worker            s/^[\w%.-]+(?:\s+[\w%.-]+)*::?//;
256*49cdfc7eSAndroid Build Coastguard Worker            s/^\t//;
257*49cdfc7eSAndroid Build Coastguard Worker            s/(?<!\$)\$\((\w+)\)/\${$1}/g;
258*49cdfc7eSAndroid Build Coastguard Worker            s/(\$){2}/$1/g;
259*49cdfc7eSAndroid Build Coastguard Worker            s/^[\s\t]*[@-]{1,2}//;
260*49cdfc7eSAndroid Build Coastguard Worker        }
261*49cdfc7eSAndroid Build Coastguard Worker
262*49cdfc7eSAndroid Build Coastguard Worker        if (
263*49cdfc7eSAndroid Build Coastguard Worker            $cat_string ne ""
264*49cdfc7eSAndroid Build Coastguard Worker            && (m/^\Q$cat_string\E$/
265*49cdfc7eSAndroid Build Coastguard Worker                || ($cat_indented && m/^\t*\Q$cat_string\E$/))
266*49cdfc7eSAndroid Build Coastguard Worker        ) {
267*49cdfc7eSAndroid Build Coastguard Worker            $cat_string = "";
268*49cdfc7eSAndroid Build Coastguard Worker            next;
269*49cdfc7eSAndroid Build Coastguard Worker        }
270*49cdfc7eSAndroid Build Coastguard Worker        my $within_another_shell = 0;
271*49cdfc7eSAndroid Build Coastguard Worker        if (m,(^|\s+)((/usr)?/bin/)?((b|d)?a|k|z|t?c)sh\s+-c\s*.+,) {
272*49cdfc7eSAndroid Build Coastguard Worker            $within_another_shell = 1;
273*49cdfc7eSAndroid Build Coastguard Worker        }
274*49cdfc7eSAndroid Build Coastguard Worker        # if cat_string is set, we are in a HERE document and need not
275*49cdfc7eSAndroid Build Coastguard Worker        # check for things
276*49cdfc7eSAndroid Build Coastguard Worker        if ($cat_string eq "" and !$within_another_shell) {
277*49cdfc7eSAndroid Build Coastguard Worker            my $found       = 0;
278*49cdfc7eSAndroid Build Coastguard Worker            my $match       = '';
279*49cdfc7eSAndroid Build Coastguard Worker            my $explanation = '';
280*49cdfc7eSAndroid Build Coastguard Worker            my $line        = $_;
281*49cdfc7eSAndroid Build Coastguard Worker
282*49cdfc7eSAndroid Build Coastguard Worker            # Remove "" / '' as they clearly aren't quoted strings
283*49cdfc7eSAndroid Build Coastguard Worker            # and not considering them makes the matching easier
284*49cdfc7eSAndroid Build Coastguard Worker            $line =~ s/(^|[^\\])(\'\')+/$1/g;
285*49cdfc7eSAndroid Build Coastguard Worker            $line =~ s/(^|[^\\])(\"\")+/$1/g;
286*49cdfc7eSAndroid Build Coastguard Worker
287*49cdfc7eSAndroid Build Coastguard Worker            if ($quote_string ne "") {
288*49cdfc7eSAndroid Build Coastguard Worker                my $otherquote = ($quote_string eq "\"" ? "\'" : "\"");
289*49cdfc7eSAndroid Build Coastguard Worker                # Inside a quoted block
290*49cdfc7eSAndroid Build Coastguard Worker                if ($line =~ /(?:^|^.*?[^\\])$quote_string(.*)$/) {
291*49cdfc7eSAndroid Build Coastguard Worker                    my $rest     = $1;
292*49cdfc7eSAndroid Build Coastguard Worker                    my $templine = $line;
293*49cdfc7eSAndroid Build Coastguard Worker
294*49cdfc7eSAndroid Build Coastguard Worker                    # Remove quoted strings delimited with $otherquote
295*49cdfc7eSAndroid Build Coastguard Worker                    $templine
296*49cdfc7eSAndroid Build Coastguard Worker                      =~ s/(^|[^\\])$otherquote[^$quote_string]*?[^\\]$otherquote/$1/g;
297*49cdfc7eSAndroid Build Coastguard Worker                    # Remove quotes that are themselves quoted
298*49cdfc7eSAndroid Build Coastguard Worker                    # "a'b"
299*49cdfc7eSAndroid Build Coastguard Worker                    $templine
300*49cdfc7eSAndroid Build Coastguard Worker                      =~ s/(^|[^\\])$otherquote.*?$quote_string.*?[^\\]$otherquote/$1/g;
301*49cdfc7eSAndroid Build Coastguard Worker                    # "\""
302*49cdfc7eSAndroid Build Coastguard Worker                    $templine
303*49cdfc7eSAndroid Build Coastguard Worker                      =~ s/(^|[^\\])$quote_string\\$quote_string$quote_string/$1/g;
304*49cdfc7eSAndroid Build Coastguard Worker
305*49cdfc7eSAndroid Build Coastguard Worker                    # After all that, were there still any quotes left?
306*49cdfc7eSAndroid Build Coastguard Worker                    my $count = () = $templine =~ /(^|[^\\])$quote_string/g;
307*49cdfc7eSAndroid Build Coastguard Worker                    next if $count == 0;
308*49cdfc7eSAndroid Build Coastguard Worker
309*49cdfc7eSAndroid Build Coastguard Worker                    $count = () = $rest =~ /(^|[^\\])$quote_string/g;
310*49cdfc7eSAndroid Build Coastguard Worker                    if ($count % 2 == 0) {
311*49cdfc7eSAndroid Build Coastguard Worker                        # Quoted block ends on this line
312*49cdfc7eSAndroid Build Coastguard Worker                        # Ignore everything before the closing quote
313*49cdfc7eSAndroid Build Coastguard Worker                        $line         = $rest || '';
314*49cdfc7eSAndroid Build Coastguard Worker                        $quote_string = "";
315*49cdfc7eSAndroid Build Coastguard Worker                    } else {
316*49cdfc7eSAndroid Build Coastguard Worker                        next;
317*49cdfc7eSAndroid Build Coastguard Worker                    }
318*49cdfc7eSAndroid Build Coastguard Worker                } else {
319*49cdfc7eSAndroid Build Coastguard Worker                    # Still inside the quoted block, skip this line
320*49cdfc7eSAndroid Build Coastguard Worker                    next;
321*49cdfc7eSAndroid Build Coastguard Worker                }
322*49cdfc7eSAndroid Build Coastguard Worker            }
323*49cdfc7eSAndroid Build Coastguard Worker
324*49cdfc7eSAndroid Build Coastguard Worker            # Check even if we removed the end of a quoted block
325*49cdfc7eSAndroid Build Coastguard Worker            # in the previous check, as a single line can end one
326*49cdfc7eSAndroid Build Coastguard Worker            # block and begin another
327*49cdfc7eSAndroid Build Coastguard Worker            if ($quote_string eq "") {
328*49cdfc7eSAndroid Build Coastguard Worker                # Possible start of a quoted block
329*49cdfc7eSAndroid Build Coastguard Worker                for my $quote ("\"", "\'") {
330*49cdfc7eSAndroid Build Coastguard Worker                    my $templine   = $line;
331*49cdfc7eSAndroid Build Coastguard Worker                    my $otherquote = ($quote eq "\"" ? "\'" : "\"");
332*49cdfc7eSAndroid Build Coastguard Worker
333*49cdfc7eSAndroid Build Coastguard Worker                    # Remove balanced quotes and their content
334*49cdfc7eSAndroid Build Coastguard Worker                    while (1) {
335*49cdfc7eSAndroid Build Coastguard Worker                        my ($length_single, $length_double) = (0, 0);
336*49cdfc7eSAndroid Build Coastguard Worker
337*49cdfc7eSAndroid Build Coastguard Worker                        # Determine which one would match first:
338*49cdfc7eSAndroid Build Coastguard Worker                        if ($templine
339*49cdfc7eSAndroid Build Coastguard Worker                            =~ m/(^.+?(?:^|[^\\\"](?:\\\\)*)\')[^\']*\'/) {
340*49cdfc7eSAndroid Build Coastguard Worker                            $length_single = length($1);
341*49cdfc7eSAndroid Build Coastguard Worker                        }
342*49cdfc7eSAndroid Build Coastguard Worker                        if ($templine
343*49cdfc7eSAndroid Build Coastguard Worker                            =~ m/(^.*?(?:^|[^\\\'](?:\\\\)*)\")(?:\\.|[^\\\"])+\"/
344*49cdfc7eSAndroid Build Coastguard Worker                        ) {
345*49cdfc7eSAndroid Build Coastguard Worker                            $length_double = length($1);
346*49cdfc7eSAndroid Build Coastguard Worker                        }
347*49cdfc7eSAndroid Build Coastguard Worker
348*49cdfc7eSAndroid Build Coastguard Worker                        # Now simplify accordingly (shorter is preferred):
349*49cdfc7eSAndroid Build Coastguard Worker                        if (
350*49cdfc7eSAndroid Build Coastguard Worker                            $length_single != 0
351*49cdfc7eSAndroid Build Coastguard Worker                            && (   $length_single < $length_double
352*49cdfc7eSAndroid Build Coastguard Worker                                || $length_double == 0)
353*49cdfc7eSAndroid Build Coastguard Worker                        ) {
354*49cdfc7eSAndroid Build Coastguard Worker                            $templine =~ s/(^|[^\\\"](?:\\\\)*)\'[^\']*\'/$1/;
355*49cdfc7eSAndroid Build Coastguard Worker                        } elsif ($length_double != 0) {
356*49cdfc7eSAndroid Build Coastguard Worker                            $templine
357*49cdfc7eSAndroid Build Coastguard Worker                              =~ s/(^|[^\\\'](?:\\\\)*)\"(?:\\.|[^\\\"])+\"/$1/;
358*49cdfc7eSAndroid Build Coastguard Worker                        } else {
359*49cdfc7eSAndroid Build Coastguard Worker                            last;
360*49cdfc7eSAndroid Build Coastguard Worker                        }
361*49cdfc7eSAndroid Build Coastguard Worker                    }
362*49cdfc7eSAndroid Build Coastguard Worker
363*49cdfc7eSAndroid Build Coastguard Worker                    # Don't flag quotes that are themselves quoted
364*49cdfc7eSAndroid Build Coastguard Worker                    # "a'b"
365*49cdfc7eSAndroid Build Coastguard Worker                    $templine =~ s/$otherquote.*?$quote.*?$otherquote//g;
366*49cdfc7eSAndroid Build Coastguard Worker                    # "\""
367*49cdfc7eSAndroid Build Coastguard Worker                    $templine =~ s/(^|[^\\])$quote\\$quote$quote/$1/g;
368*49cdfc7eSAndroid Build Coastguard Worker                    # \' or \"
369*49cdfc7eSAndroid Build Coastguard Worker                    $templine =~ s/\\[\'\"]//g;
370*49cdfc7eSAndroid Build Coastguard Worker                    my $count = () = $templine =~ /(^|(?!\\))$quote/g;
371*49cdfc7eSAndroid Build Coastguard Worker
372*49cdfc7eSAndroid Build Coastguard Worker                    # If there's an odd number of non-escaped
373*49cdfc7eSAndroid Build Coastguard Worker                    # quotes in the line it's almost certainly the
374*49cdfc7eSAndroid Build Coastguard Worker                    # start of a quoted block.
375*49cdfc7eSAndroid Build Coastguard Worker                    if ($count % 2 == 1) {
376*49cdfc7eSAndroid Build Coastguard Worker                        $quote_string = $quote;
377*49cdfc7eSAndroid Build Coastguard Worker                        $start_lines{'quote_string'} = $.;
378*49cdfc7eSAndroid Build Coastguard Worker                        $line =~ s/^(.*)$quote.*$/$1/;
379*49cdfc7eSAndroid Build Coastguard Worker                        last;
380*49cdfc7eSAndroid Build Coastguard Worker                    }
381*49cdfc7eSAndroid Build Coastguard Worker                }
382*49cdfc7eSAndroid Build Coastguard Worker            }
383*49cdfc7eSAndroid Build Coastguard Worker
384*49cdfc7eSAndroid Build Coastguard Worker            # since this test is ugly, I have to do it by itself
385*49cdfc7eSAndroid Build Coastguard Worker            # detect source (.) trying to pass args to the command it runs
386*49cdfc7eSAndroid Build Coastguard Worker            # The first expression weeds out '. "foo bar"'
387*49cdfc7eSAndroid Build Coastguard Worker            if (    not $found
388*49cdfc7eSAndroid Build Coastguard Worker                and not
389*49cdfc7eSAndroid Build Coastguard Workerm/$LEADIN\.\s+(\"[^\"]+\"|\'[^\']+\'|\$\([^)]+\)+(?:\/[^\s;]+)?)\s*(\&|\||\d?>|<|;|\Z)/o
390*49cdfc7eSAndroid Build Coastguard Worker                and m/$LEADIN(\.\s+[^\s;\`:]+\s+([^\s;]+))/o) {
391*49cdfc7eSAndroid Build Coastguard Worker                if ($2 =~ /^(\&|\||\d?>|<)/) {
392*49cdfc7eSAndroid Build Coastguard Worker                    # everything is ok
393*49cdfc7eSAndroid Build Coastguard Worker                    ;
394*49cdfc7eSAndroid Build Coastguard Worker                } else {
395*49cdfc7eSAndroid Build Coastguard Worker                    $found       = 1;
396*49cdfc7eSAndroid Build Coastguard Worker                    $match       = $1;
397*49cdfc7eSAndroid Build Coastguard Worker                    $explanation = "sourced script with arguments";
398*49cdfc7eSAndroid Build Coastguard Worker                    output_explanation($display_filename, $orig_line,
399*49cdfc7eSAndroid Build Coastguard Worker                        $explanation);
400*49cdfc7eSAndroid Build Coastguard Worker                }
401*49cdfc7eSAndroid Build Coastguard Worker            }
402*49cdfc7eSAndroid Build Coastguard Worker
403*49cdfc7eSAndroid Build Coastguard Worker            # Remove "quoted quotes". They're likely to be inside
404*49cdfc7eSAndroid Build Coastguard Worker            # another pair of quotes; we're not interested in
405*49cdfc7eSAndroid Build Coastguard Worker            # them for their own sake and removing them makes finding
406*49cdfc7eSAndroid Build Coastguard Worker            # the limits of the outer pair far easier.
407*49cdfc7eSAndroid Build Coastguard Worker            $line =~ s/(^|[^\\\'\"])\"\'\"/$1/g;
408*49cdfc7eSAndroid Build Coastguard Worker            $line =~ s/(^|[^\\\'\"])\'\"\'/$1/g;
409*49cdfc7eSAndroid Build Coastguard Worker
410*49cdfc7eSAndroid Build Coastguard Worker            foreach my $re (@singlequote_bashisms_keys) {
411*49cdfc7eSAndroid Build Coastguard Worker                my $expl = $singlequote_bashisms{$re};
412*49cdfc7eSAndroid Build Coastguard Worker                if ($line =~ m/($re)/) {
413*49cdfc7eSAndroid Build Coastguard Worker                    $found       = 1;
414*49cdfc7eSAndroid Build Coastguard Worker                    $match       = $1;
415*49cdfc7eSAndroid Build Coastguard Worker                    $explanation = $expl;
416*49cdfc7eSAndroid Build Coastguard Worker                    output_explanation($display_filename, $orig_line,
417*49cdfc7eSAndroid Build Coastguard Worker                        $explanation);
418*49cdfc7eSAndroid Build Coastguard Worker                }
419*49cdfc7eSAndroid Build Coastguard Worker            }
420*49cdfc7eSAndroid Build Coastguard Worker
421*49cdfc7eSAndroid Build Coastguard Worker            my $re = '(?<![\$\\\])\$\'[^\']+\'';
422*49cdfc7eSAndroid Build Coastguard Worker            if ($line =~ m/(.*)($re)/o) {
423*49cdfc7eSAndroid Build Coastguard Worker                my $count = () = $1 =~ /(^|[^\\])\'/g;
424*49cdfc7eSAndroid Build Coastguard Worker                if ($count % 2 == 0) {
425*49cdfc7eSAndroid Build Coastguard Worker                    output_explanation($display_filename, $orig_line,
426*49cdfc7eSAndroid Build Coastguard Worker                        q<$'...' should be "$(printf '...')">);
427*49cdfc7eSAndroid Build Coastguard Worker                }
428*49cdfc7eSAndroid Build Coastguard Worker            }
429*49cdfc7eSAndroid Build Coastguard Worker
430*49cdfc7eSAndroid Build Coastguard Worker            # $cat_line contains the version of the line we'll check
431*49cdfc7eSAndroid Build Coastguard Worker            # for heredoc delimiters later. Initially, remove any
432*49cdfc7eSAndroid Build Coastguard Worker            # spaces between << and the delimiter to make the following
433*49cdfc7eSAndroid Build Coastguard Worker            # updates to $cat_line easier. However, don't remove the
434*49cdfc7eSAndroid Build Coastguard Worker            # spaces if the delimiter starts with a -, as that changes
435*49cdfc7eSAndroid Build Coastguard Worker            # how the delimiter is searched.
436*49cdfc7eSAndroid Build Coastguard Worker            my $cat_line = $line;
437*49cdfc7eSAndroid Build Coastguard Worker            $cat_line =~ s/(<\<-?)\s+(?!-)/$1/g;
438*49cdfc7eSAndroid Build Coastguard Worker
439*49cdfc7eSAndroid Build Coastguard Worker            # Ignore anything inside single quotes; it could be an
440*49cdfc7eSAndroid Build Coastguard Worker            # argument to grep or the like.
441*49cdfc7eSAndroid Build Coastguard Worker            $line =~ s/(^|[^\\\"](?:\\\\)*)\'(?:\\.|[^\\\'])+\'/$1''/g;
442*49cdfc7eSAndroid Build Coastguard Worker
443*49cdfc7eSAndroid Build Coastguard Worker            # As above, with the exception that we don't remove the string
444*49cdfc7eSAndroid Build Coastguard Worker            # if the quote is immediately preceded by a < or a -, so we
445*49cdfc7eSAndroid Build Coastguard Worker            # can match "foo <<-?'xyz'" as a heredoc later
446*49cdfc7eSAndroid Build Coastguard Worker            # The check is a little more greedy than we'd like, but the
447*49cdfc7eSAndroid Build Coastguard Worker            # heredoc test itself will weed out any false positives
448*49cdfc7eSAndroid Build Coastguard Worker            $cat_line =~ s/(^|[^<\\\"-](?:\\\\)*)\'(?:\\.|[^\\\'])+\'/$1''/g;
449*49cdfc7eSAndroid Build Coastguard Worker
450*49cdfc7eSAndroid Build Coastguard Worker            $re = '(?<![\$\\\])\$\"[^\"]+\"';
451*49cdfc7eSAndroid Build Coastguard Worker            if ($line =~ m/(.*)($re)/o) {
452*49cdfc7eSAndroid Build Coastguard Worker                my $count = () = $1 =~ /(^|[^\\])\"/g;
453*49cdfc7eSAndroid Build Coastguard Worker                if ($count % 2 == 0) {
454*49cdfc7eSAndroid Build Coastguard Worker                    output_explanation($display_filename, $orig_line,
455*49cdfc7eSAndroid Build Coastguard Worker                        q<$"foo" should be eval_gettext "foo">);
456*49cdfc7eSAndroid Build Coastguard Worker                }
457*49cdfc7eSAndroid Build Coastguard Worker            }
458*49cdfc7eSAndroid Build Coastguard Worker
459*49cdfc7eSAndroid Build Coastguard Worker            foreach my $re (@string_bashisms_keys) {
460*49cdfc7eSAndroid Build Coastguard Worker                my $expl = $string_bashisms{$re};
461*49cdfc7eSAndroid Build Coastguard Worker                if ($line =~ m/($re)/) {
462*49cdfc7eSAndroid Build Coastguard Worker                    $found       = 1;
463*49cdfc7eSAndroid Build Coastguard Worker                    $match       = $1;
464*49cdfc7eSAndroid Build Coastguard Worker                    $explanation = $expl;
465*49cdfc7eSAndroid Build Coastguard Worker                    output_explanation($display_filename, $orig_line,
466*49cdfc7eSAndroid Build Coastguard Worker                        $explanation);
467*49cdfc7eSAndroid Build Coastguard Worker                }
468*49cdfc7eSAndroid Build Coastguard Worker            }
469*49cdfc7eSAndroid Build Coastguard Worker
470*49cdfc7eSAndroid Build Coastguard Worker            # We've checked for all the things we still want to notice in
471*49cdfc7eSAndroid Build Coastguard Worker            # double-quoted strings, so now remove those strings as well.
472*49cdfc7eSAndroid Build Coastguard Worker            $line     =~ s/(^|[^\\\'](?:\\\\)*)\"(?:\\.|[^\\\"])+\"/$1""/g;
473*49cdfc7eSAndroid Build Coastguard Worker            $cat_line =~ s/(^|[^<\\\'-](?:\\\\)*)\"(?:\\.|[^\\\"])+\"/$1""/g;
474*49cdfc7eSAndroid Build Coastguard Worker            foreach my $re (@bashisms_keys) {
475*49cdfc7eSAndroid Build Coastguard Worker                my $expl = $bashisms{$re};
476*49cdfc7eSAndroid Build Coastguard Worker                if ($line =~ m/($re)/) {
477*49cdfc7eSAndroid Build Coastguard Worker                    $found       = 1;
478*49cdfc7eSAndroid Build Coastguard Worker                    $match       = $1;
479*49cdfc7eSAndroid Build Coastguard Worker                    $explanation = $expl;
480*49cdfc7eSAndroid Build Coastguard Worker                    output_explanation($display_filename, $orig_line,
481*49cdfc7eSAndroid Build Coastguard Worker                        $explanation);
482*49cdfc7eSAndroid Build Coastguard Worker                }
483*49cdfc7eSAndroid Build Coastguard Worker            }
484*49cdfc7eSAndroid Build Coastguard Worker            # This check requires the value to be compared, which could
485*49cdfc7eSAndroid Build Coastguard Worker            # be done in the regex itself but requires "use re 'eval'".
486*49cdfc7eSAndroid Build Coastguard Worker            # So it's better done in its own
487*49cdfc7eSAndroid Build Coastguard Worker            if ($line =~ m/$LEADIN((?:exit|return)\s+(\d{3,}))/o && $2 > 255) {
488*49cdfc7eSAndroid Build Coastguard Worker                $explanation = 'exit|return status code greater than 255';
489*49cdfc7eSAndroid Build Coastguard Worker                output_explanation($display_filename, $orig_line,
490*49cdfc7eSAndroid Build Coastguard Worker                    $explanation);
491*49cdfc7eSAndroid Build Coastguard Worker            }
492*49cdfc7eSAndroid Build Coastguard Worker
493*49cdfc7eSAndroid Build Coastguard Worker            # Only look for the beginning of a heredoc here, after we've
494*49cdfc7eSAndroid Build Coastguard Worker            # stripped out quoted material, to avoid false positives.
495*49cdfc7eSAndroid Build Coastguard Worker            if ($cat_line
496*49cdfc7eSAndroid Build Coastguard Worker                =~ m/(?:^|[^<])\<\<(\-?)\s*(?:(?!<|'|")((?:[^\s;>|]+(?:(?<=\\)[\s;>|])?)+)|[\'\"](.*?)[\'\"])/
497*49cdfc7eSAndroid Build Coastguard Worker            ) {
498*49cdfc7eSAndroid Build Coastguard Worker                $cat_indented = ($1 && $1 eq '-') ? 1 : 0;
499*49cdfc7eSAndroid Build Coastguard Worker                my $quoted = defined($3);
500*49cdfc7eSAndroid Build Coastguard Worker                $cat_string = $quoted ? $3 : $2;
501*49cdfc7eSAndroid Build Coastguard Worker                unless ($quoted) {
502*49cdfc7eSAndroid Build Coastguard Worker                    # Now strip backslashes. Keep the position of the
503*49cdfc7eSAndroid Build Coastguard Worker                    # last match in a variable, as s/// resets it back
504*49cdfc7eSAndroid Build Coastguard Worker                    # to undef, but we don't want that.
505*49cdfc7eSAndroid Build Coastguard Worker                    my $pos = 0;
506*49cdfc7eSAndroid Build Coastguard Worker                    pos($cat_string) = $pos;
507*49cdfc7eSAndroid Build Coastguard Worker                    while ($cat_string =~ s/\G(.*?)\\/$1/) {
508*49cdfc7eSAndroid Build Coastguard Worker                        # position += length of match + the character
509*49cdfc7eSAndroid Build Coastguard Worker                        # that followed the backslash:
510*49cdfc7eSAndroid Build Coastguard Worker                        $pos += length($1) + 1;
511*49cdfc7eSAndroid Build Coastguard Worker                        pos($cat_string) = $pos;
512*49cdfc7eSAndroid Build Coastguard Worker                    }
513*49cdfc7eSAndroid Build Coastguard Worker                }
514*49cdfc7eSAndroid Build Coastguard Worker                $start_lines{'cat_string'} = $.;
515*49cdfc7eSAndroid Build Coastguard Worker            }
516*49cdfc7eSAndroid Build Coastguard Worker        }
517*49cdfc7eSAndroid Build Coastguard Worker    }
518*49cdfc7eSAndroid Build Coastguard Worker
519*49cdfc7eSAndroid Build Coastguard Worker    warn
520*49cdfc7eSAndroid Build Coastguard Worker"error: $display_filename:  Unterminated heredoc found, EOF reached. Wanted: <$cat_string>, opened in line $start_lines{'cat_string'}\n"
521*49cdfc7eSAndroid Build Coastguard Worker      if ($cat_string ne '');
522*49cdfc7eSAndroid Build Coastguard Worker    warn
523*49cdfc7eSAndroid Build Coastguard Worker"error: $display_filename: Unterminated quoted string found, EOF reached. Wanted: <$quote_string>, opened in line $start_lines{'quote_string'}\n"
524*49cdfc7eSAndroid Build Coastguard Worker      if ($quote_string ne '');
525*49cdfc7eSAndroid Build Coastguard Worker    warn "error: $display_filename: EOF reached while on line continuation.\n"
526*49cdfc7eSAndroid Build Coastguard Worker      if ($buffered_line ne '');
527*49cdfc7eSAndroid Build Coastguard Worker
528*49cdfc7eSAndroid Build Coastguard Worker    close C;
529*49cdfc7eSAndroid Build Coastguard Worker
530*49cdfc7eSAndroid Build Coastguard Worker    if ($mode && !$issues) {
531*49cdfc7eSAndroid Build Coastguard Worker        warn "could not find any possible bashisms in bash script $filename\n";
532*49cdfc7eSAndroid Build Coastguard Worker        $status |= 4;
533*49cdfc7eSAndroid Build Coastguard Worker    }
534*49cdfc7eSAndroid Build Coastguard Worker}
535*49cdfc7eSAndroid Build Coastguard Worker
536*49cdfc7eSAndroid Build Coastguard Workerexit $status;
537*49cdfc7eSAndroid Build Coastguard Worker
538*49cdfc7eSAndroid Build Coastguard Workersub output_explanation {
539*49cdfc7eSAndroid Build Coastguard Worker    my ($filename, $line, $explanation) = @_;
540*49cdfc7eSAndroid Build Coastguard Worker
541*49cdfc7eSAndroid Build Coastguard Worker    if ($mode) {
542*49cdfc7eSAndroid Build Coastguard Worker        # When examining a bash script, just flag that there are indeed
543*49cdfc7eSAndroid Build Coastguard Worker        # bashisms present
544*49cdfc7eSAndroid Build Coastguard Worker        $issues = 1;
545*49cdfc7eSAndroid Build Coastguard Worker    } else {
546*49cdfc7eSAndroid Build Coastguard Worker        warn "possible bashism in $filename line $. ($explanation):\n$line\n";
547*49cdfc7eSAndroid Build Coastguard Worker        if ($opt_early_fail) {
548*49cdfc7eSAndroid Build Coastguard Worker            exit 1;
549*49cdfc7eSAndroid Build Coastguard Worker        }
550*49cdfc7eSAndroid Build Coastguard Worker        $status |= 1;
551*49cdfc7eSAndroid Build Coastguard Worker    }
552*49cdfc7eSAndroid Build Coastguard Worker}
553*49cdfc7eSAndroid Build Coastguard Worker
554*49cdfc7eSAndroid Build Coastguard Worker# Returns non-zero if the given file is not actually a shell script,
555*49cdfc7eSAndroid Build Coastguard Worker# just looks like one.
556*49cdfc7eSAndroid Build Coastguard Workersub script_is_evil_and_wrong {
557*49cdfc7eSAndroid Build Coastguard Worker    my ($filename) = @_;
558*49cdfc7eSAndroid Build Coastguard Worker    my $ret = -1;
559*49cdfc7eSAndroid Build Coastguard Worker    # lintian's version of this function aborts if the file
560*49cdfc7eSAndroid Build Coastguard Worker    # can't be opened, but we simply return as the next
561*49cdfc7eSAndroid Build Coastguard Worker    # test in the calling code handles reporting the error
562*49cdfc7eSAndroid Build Coastguard Worker    # itself
563*49cdfc7eSAndroid Build Coastguard Worker    open(IN, '<', $filename) or return $ret;
564*49cdfc7eSAndroid Build Coastguard Worker    my $i            = 0;
565*49cdfc7eSAndroid Build Coastguard Worker    my $var          = "0";
566*49cdfc7eSAndroid Build Coastguard Worker    my $backgrounded = 0;
567*49cdfc7eSAndroid Build Coastguard Worker    local $_;
568*49cdfc7eSAndroid Build Coastguard Worker    while (<IN>) {
569*49cdfc7eSAndroid Build Coastguard Worker        chomp;
570*49cdfc7eSAndroid Build Coastguard Worker        next if /^#/o;
571*49cdfc7eSAndroid Build Coastguard Worker        next if /^$/o;
572*49cdfc7eSAndroid Build Coastguard Worker        last if (++$i > 55);
573*49cdfc7eSAndroid Build Coastguard Worker        if (
574*49cdfc7eSAndroid Build Coastguard Worker            m~
575*49cdfc7eSAndroid Build Coastguard Worker	    # the exec should either be "eval"ed or a new statement
576*49cdfc7eSAndroid Build Coastguard Worker	    (^\s*|\beval\s*[\'\"]|(;|&&|\b(then|else))\s*)
577*49cdfc7eSAndroid Build Coastguard Worker
578*49cdfc7eSAndroid Build Coastguard Worker	    # eat anything between the exec and $0
579*49cdfc7eSAndroid Build Coastguard Worker	    exec\s*.+\s*
580*49cdfc7eSAndroid Build Coastguard Worker
581*49cdfc7eSAndroid Build Coastguard Worker	    # optionally quoted executable name (via $0)
582*49cdfc7eSAndroid Build Coastguard Worker	    .?\$$var.?\s*
583*49cdfc7eSAndroid Build Coastguard Worker
584*49cdfc7eSAndroid Build Coastguard Worker	    # optional "end of options" indicator
585*49cdfc7eSAndroid Build Coastguard Worker	    (--\s*)?
586*49cdfc7eSAndroid Build Coastguard Worker
587*49cdfc7eSAndroid Build Coastguard Worker	    # Match expressions of the form '${1+$@}', '${1:+"$@"',
588*49cdfc7eSAndroid Build Coastguard Worker	    # '"${1+$@', "$@", etc where the quotes (before the dollar
589*49cdfc7eSAndroid Build Coastguard Worker	    # sign(s)) are optional and the second (or only if the $1
590*49cdfc7eSAndroid Build Coastguard Worker	    # clause is omitted) parameter may be $@ or $*.
591*49cdfc7eSAndroid Build Coastguard Worker	    #
592*49cdfc7eSAndroid Build Coastguard Worker	    # Finally the whole subexpression may be omitted for scripts
593*49cdfc7eSAndroid Build Coastguard Worker	    # which do not pass on their parameters (i.e. after re-execing
594*49cdfc7eSAndroid Build Coastguard Worker	    # they take their parameters (and potentially data) from stdin
595*49cdfc7eSAndroid Build Coastguard Worker	    .?(\$\{1:?\+.?)?(\$(\@|\*))?~x
596*49cdfc7eSAndroid Build Coastguard Worker        ) {
597*49cdfc7eSAndroid Build Coastguard Worker            $ret = $. - 1;
598*49cdfc7eSAndroid Build Coastguard Worker            last;
599*49cdfc7eSAndroid Build Coastguard Worker        } elsif (/^\s*(\w+)=\$0;/) {
600*49cdfc7eSAndroid Build Coastguard Worker            $var = $1;
601*49cdfc7eSAndroid Build Coastguard Worker        } elsif (
602*49cdfc7eSAndroid Build Coastguard Worker            m~
603*49cdfc7eSAndroid Build Coastguard Worker	    # Match scripts which use "foo $0 $@ &\nexec true\n"
604*49cdfc7eSAndroid Build Coastguard Worker	    # Program name
605*49cdfc7eSAndroid Build Coastguard Worker	    \S+\s+
606*49cdfc7eSAndroid Build Coastguard Worker
607*49cdfc7eSAndroid Build Coastguard Worker	    # As above
608*49cdfc7eSAndroid Build Coastguard Worker	    .?\$$var.?\s*
609*49cdfc7eSAndroid Build Coastguard Worker	    (--\s*)?
610*49cdfc7eSAndroid Build Coastguard Worker	    .?(\$\{1:?\+.?)?(\$(\@|\*))?.?\s*\&~x
611*49cdfc7eSAndroid Build Coastguard Worker        ) {
612*49cdfc7eSAndroid Build Coastguard Worker
613*49cdfc7eSAndroid Build Coastguard Worker            $backgrounded = 1;
614*49cdfc7eSAndroid Build Coastguard Worker        } elsif (
615*49cdfc7eSAndroid Build Coastguard Worker            $backgrounded
616*49cdfc7eSAndroid Build Coastguard Worker            and m~
617*49cdfc7eSAndroid Build Coastguard Worker	    # the exec should either be "eval"ed or a new statement
618*49cdfc7eSAndroid Build Coastguard Worker	    (^\s*|\beval\s*[\'\"]|(;|&&|\b(then|else))\s*)
619*49cdfc7eSAndroid Build Coastguard Worker	    exec\s+true(\s|\Z)~x
620*49cdfc7eSAndroid Build Coastguard Worker        ) {
621*49cdfc7eSAndroid Build Coastguard Worker
622*49cdfc7eSAndroid Build Coastguard Worker            $ret = $. - 1;
623*49cdfc7eSAndroid Build Coastguard Worker            last;
624*49cdfc7eSAndroid Build Coastguard Worker        } elsif (m~\@DPATCH\@~) {
625*49cdfc7eSAndroid Build Coastguard Worker            $ret = $. - 1;
626*49cdfc7eSAndroid Build Coastguard Worker            last;
627*49cdfc7eSAndroid Build Coastguard Worker        }
628*49cdfc7eSAndroid Build Coastguard Worker
629*49cdfc7eSAndroid Build Coastguard Worker    }
630*49cdfc7eSAndroid Build Coastguard Worker    close IN;
631*49cdfc7eSAndroid Build Coastguard Worker    return $ret;
632*49cdfc7eSAndroid Build Coastguard Worker}
633*49cdfc7eSAndroid Build Coastguard Worker
634*49cdfc7eSAndroid Build Coastguard Workersub init_hashes {
635*49cdfc7eSAndroid Build Coastguard Worker
636*49cdfc7eSAndroid Build Coastguard Worker    %bashisms = (
637*49cdfc7eSAndroid Build Coastguard Worker        qr'(?:^|\s+)function [^<>\(\)\[\]\{\};|\s]+(\s|\(|\Z)' =>
638*49cdfc7eSAndroid Build Coastguard Worker          q<'function' is useless>,
639*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'select\s+\w+'               => q<'select' is not POSIX>,
640*49cdfc7eSAndroid Build Coastguard Worker        qr'(test|-o|-a)\s*[^\s]+\s+==\s'         => q<should be 'b = a'>,
641*49cdfc7eSAndroid Build Coastguard Worker        qr'\[\s+[^\]]+\s+==\s'                   => q<should be 'b = a'>,
642*49cdfc7eSAndroid Build Coastguard Worker        qr'\s\|\&'                               => q<pipelining is not POSIX>,
643*49cdfc7eSAndroid Build Coastguard Worker        qr'[^\\\$]\{([^\s\\\}]*?,)+[^\\\}\s]*\}' => q<brace expansion>,
644*49cdfc7eSAndroid Build Coastguard Worker        qr'\{\d+\.\.\d+(?:\.\.\d+)?\}' =>
645*49cdfc7eSAndroid Build Coastguard Worker          q<brace expansion, {a..b[..c]}should be $(seq a [c] b)>,
646*49cdfc7eSAndroid Build Coastguard Worker        qr'(?i)\{[a-z]\.\.[a-z](?:\.\.\d+)?\}' => q<brace expansion>,
647*49cdfc7eSAndroid Build Coastguard Worker        qr'(?:^|\s+)\w+\[\d+\]='               => q<bash arrays, H[0]>,
648*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN
649*49cdfc7eSAndroid Build Coastguard Worker          . qr'read\s+(?:-[a-qs-zA-Z\d-]+)' =>
650*49cdfc7eSAndroid Build Coastguard Worker          q<read with option other than -r>,
651*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN
652*49cdfc7eSAndroid Build Coastguard Worker          . qr'read\s*(?:-\w+\s*)*(?:\".*?\"|[\'].*?[\'])?\s*(?:;|$)' =>
653*49cdfc7eSAndroid Build Coastguard Worker          q<read without variable>,
654*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'echo\s+(-n\s+)?-n?en?\s' => q<echo -e>,
655*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'exec\s+-[acl]'           => q<exec -c/-l/-a name>,
656*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'let\s'                   => q<let ...>,
657*49cdfc7eSAndroid Build Coastguard Worker        qr'(?<![\$\(])\(\(.*\)\)'             => q<'((' should be '$(('>,
658*49cdfc7eSAndroid Build Coastguard Worker        qr'(?:^|\s+)(\[|test)\s+-a' => q<test with unary -a (should be -e)>,
659*49cdfc7eSAndroid Build Coastguard Worker        qr'\&>'                     => q<should be \>word 2\>&1>,
660*49cdfc7eSAndroid Build Coastguard Worker        qr'(<\&|>\&)\s*((-|\d+)[^\s;|)}`&\\\\]|[^-\d\s]+(?<!\$)(?!\d))' =>
661*49cdfc7eSAndroid Build Coastguard Worker          q<should be \>word 2\>&1>,
662*49cdfc7eSAndroid Build Coastguard Worker        qr'\[\[(?!:)' =>
663*49cdfc7eSAndroid Build Coastguard Worker          q<alternative test command ([[ foo ]] should be [ foo ])>,
664*49cdfc7eSAndroid Build Coastguard Worker        qr'/dev/(tcp|udp)'               => q</dev/(tcp|udp)>,
665*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'builtin\s'          => q<builtin>,
666*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'caller\s'           => q<caller>,
667*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'compgen\s'          => q<compgen>,
668*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'complete\s'         => q<complete>,
669*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'declare\s'          => q<declare>,
670*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'dirs(\s|\Z)'        => q<dirs>,
671*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'disown\s'           => q<disown>,
672*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'enable\s'           => q<enable>,
673*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'mapfile\s'          => q<mapfile>,
674*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'readarray\s'        => q<readarray>,
675*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'shopt(\s|\Z)'       => q<shopt>,
676*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'suspend\s'          => q<suspend>,
677*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'time\s'             => q<time>,
678*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'type\s'             => q<type>,
679*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'typeset\s'          => q<typeset>,
680*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'ulimit(\s|\Z)'      => q<ulimit>,
681*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'set\s+-[BHT]+'      => q<set -[BHT]>,
682*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'alias\s+-p'         => q<alias -p>,
683*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'unalias\s+-a'       => q<unalias -a>,
684*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'local\s+-[a-zA-Z]+' => q<local -opt>,
685*49cdfc7eSAndroid Build Coastguard Worker        # function '=' is special-cased due to bash arrays (think of "foo=()")
686*49cdfc7eSAndroid Build Coastguard Worker        qr'(?:^|\s)\s*=\s*\(\s*\)\s*([\{|\(]|\Z)' =>
687*49cdfc7eSAndroid Build Coastguard Worker          q<function names should only contain [a-z0-9_]>,
688*49cdfc7eSAndroid Build Coastguard Workerqr'(?:^|\s)(?<func>function\s)?\s*(?:[^<>\(\)\[\]\{\};|\s]*[^<>\(\)\[\]\{\};|\s\w][^<>\(\)\[\]\{\};|\s]*)(?(<func>)(?=)|(?<!=))\s*(?(<func>)(?:\(\s*\))?|\(\s*\))\s*([\{|\(]|\Z)'
689*49cdfc7eSAndroid Build Coastguard Worker          => q<function names should only contain [a-z0-9_]>,
690*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'(push|pop)d(\s|\Z)' => q<(push|pop)d>,
691*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'export\s+-[^p]'   => q<export only takes -p as an option>,
692*49cdfc7eSAndroid Build Coastguard Worker        qr'(?:^|\s+)[<>]\(.*?\)'       => q<\<() process substitution>,
693*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'readonly\s+-[af]' => q<readonly -[af]>,
694*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'(sh|\$\{?SHELL\}?) -[rD]' => q<sh -[rD]>,
695*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'(sh|\$\{?SHELL\}?) --\w+' => q<sh --long-option>,
696*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'(sh|\$\{?SHELL\}?) [-+]O' => q<sh [-+]O>,
697*49cdfc7eSAndroid Build Coastguard Worker        qr'\[\^[^]]+\]'                        => q<[^] should be [!]>,
698*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN
699*49cdfc7eSAndroid Build Coastguard Worker          . qr'printf\s+-v' =>
700*49cdfc7eSAndroid Build Coastguard Worker          q<'printf -v var ...' should be var='$(printf ...)'>,
701*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'coproc\s' => q<coproc>,
702*49cdfc7eSAndroid Build Coastguard Worker        qr';;?&'               => q<;;& and ;& special case operators>,
703*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'jobs\s'   => q<jobs>,
704*49cdfc7eSAndroid Build Coastguard Worker #	$LEADIN . qr'jobs\s+-[^lp]\s' =>  q<'jobs' with option other than -l or -p>,
705*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN
706*49cdfc7eSAndroid Build Coastguard Worker          . qr'command\s+(?:-[pvV]+\s+)*-(?:[pvV])*[^pvV\s]' =>
707*49cdfc7eSAndroid Build Coastguard Worker          q<'command' with option other than -p, -v or -V>,
708*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN
709*49cdfc7eSAndroid Build Coastguard Worker          . qr'setvar\s' =>
710*49cdfc7eSAndroid Build Coastguard Worker          q<setvar 'foo' 'bar' should be eval 'foo="'"$bar"'"'>,
711*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN
712*49cdfc7eSAndroid Build Coastguard Worker          . qr'trap\s+["\']?.*["\']?\s+.*(?:ERR|DEBUG|RETURN)' =>
713*49cdfc7eSAndroid Build Coastguard Worker          q<trap with ERR|DEBUG|RETURN>,
714*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN
715*49cdfc7eSAndroid Build Coastguard Worker          . qr'(?:exit|return)\s+-\d' =>
716*49cdfc7eSAndroid Build Coastguard Worker          q<exit|return with negative status code>,
717*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN
718*49cdfc7eSAndroid Build Coastguard Worker          . qr'(?:exit|return)\s+--' =>
719*49cdfc7eSAndroid Build Coastguard Worker          q<'exit --' should be 'exit' (idem for return)>,
720*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'hash(\s|\Z)' => q<hash>,
721*49cdfc7eSAndroid Build Coastguard Worker        qr'(?:[:=\s])~(?:[+-]|[+-]?\d+)(?:[/\s]|\Z)' =>
722*49cdfc7eSAndroid Build Coastguard Worker          q<non-standard tilde expansion>,
723*49cdfc7eSAndroid Build Coastguard Worker    );
724*49cdfc7eSAndroid Build Coastguard Worker
725*49cdfc7eSAndroid Build Coastguard Worker    %string_bashisms = (
726*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\[[^][]+\]' => q<'$[' should be '$(('>,
727*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\{(?:\w+|@|\*)\:(?:\d+|\$\{?\w+\}?)+(?::(?:\d+|\$\{?\w+\}?)+)?\}'
728*49cdfc7eSAndroid Build Coastguard Worker          => q<${foo:3[:1]}>,
729*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\{!\w+[\@*]\}' => q<${!prefix[*|@]>,
730*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\{!\w+\}'      => q<${!name}>,
731*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\{(?:\w+|@|\*)([,^]{1,2}.*?)\}' =>
732*49cdfc7eSAndroid Build Coastguard Worker          q<${parm,[,][pat]} or ${parm^[^][pat]}>,
733*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\{[@*]([#%]{1,2}.*?)\}' => q<${[@|*]#[#]pat} or ${[@|*]%[%]pat}>,
734*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\{#[@*]\}'              => q<${#@} or ${#*}>,
735*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\{(?:\w+|@|\*)(/.+?){1,2}\}' => q<${parm/?/pat[/str]}>,
736*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\{\#?\w+\[.+\](?:[/,:#%^].+?)?\}' =>
737*49cdfc7eSAndroid Build Coastguard Worker          q<bash arrays, ${name[0|*|@]}>,
738*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\{?RANDOM\}?\b'          => q<$RANDOM>,
739*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\{?(OS|MACH)TYPE\}?\b'   => q<$(OS|MACH)TYPE>,
740*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\{?HOST(TYPE|NAME)\}?\b' => q<$HOST(TYPE|NAME)>,
741*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\{?DIRSTACK\}?\b'        => q<$DIRSTACK>,
742*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\{?EUID\}?\b'            => q<$EUID should be "$(id -u)">,
743*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\{?UID\}?\b'             => q<$UID should be "$(id -ru)">,
744*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\{?SECONDS\}?\b'         => q<$SECONDS>,
745*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\{?BASH_[A-Z]+\}?\b'     => q<$BASH_SOMETHING>,
746*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\{?SHELLOPTS\}?\b'       => q<$SHELLOPTS>,
747*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\{?PIPESTATUS\}?\b'      => q<$PIPESTATUS>,
748*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\{?SHLVL\}?\b'           => q<$SHLVL>,
749*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\{?FUNCNAME\}?\b'        => q<$FUNCNAME>,
750*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\{?TMOUT\}?\b'           => q<$TMOUT>,
751*49cdfc7eSAndroid Build Coastguard Worker        qr'(?:^|\s+)TMOUT='           => q<TMOUT=>,
752*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\{?TIMEFORMAT\}?\b'      => q<$TIMEFORMAT>,
753*49cdfc7eSAndroid Build Coastguard Worker        qr'(?:^|\s+)TIMEFORMAT='      => q<TIMEFORMAT=>,
754*49cdfc7eSAndroid Build Coastguard Worker        qr'(?<![$\\])\$\{?_\}?\b'     => q<$_>,
755*49cdfc7eSAndroid Build Coastguard Worker        qr'(?:^|\s+)GLOBIGNORE='      => q<GLOBIGNORE=>,
756*49cdfc7eSAndroid Build Coastguard Worker        qr'<<<'                       => q<\<\<\< here string>,
757*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN
758*49cdfc7eSAndroid Build Coastguard Worker          . qr'echo\s+(?:-[^e\s]+\s+)?\"[^\"]*(\\[abcEfnrtv0])+.*?[\"]' =>
759*49cdfc7eSAndroid Build Coastguard Worker          q<unsafe echo with backslash>,
760*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\(\([\s\w$*/+-]*\w\+\+.*?\)\)' =>
761*49cdfc7eSAndroid Build Coastguard Worker          q<'$((n++))' should be '$n; $((n=n+1))'>,
762*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\(\([\s\w$*/+-]*\+\+\w.*?\)\)' =>
763*49cdfc7eSAndroid Build Coastguard Worker          q<'$((++n))' should be '$((n=n+1))'>,
764*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\(\([\s\w$*/+-]*\w\-\-.*?\)\)' =>
765*49cdfc7eSAndroid Build Coastguard Worker          q<'$((n--))' should be '$n; $((n=n-1))'>,
766*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\(\([\s\w$*/+-]*\-\-\w.*?\)\)' =>
767*49cdfc7eSAndroid Build Coastguard Worker          q<'$((--n))' should be '$((n=n-1))'>,
768*49cdfc7eSAndroid Build Coastguard Worker        qr'\$\(\([\s\w$*/+-]*\*\*.*?\)\)' => q<exponentiation is not POSIX>,
769*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN . qr'printf\s["\'][^"\']*?%q.+?["\']' => q<printf %q>,
770*49cdfc7eSAndroid Build Coastguard Worker    );
771*49cdfc7eSAndroid Build Coastguard Worker
772*49cdfc7eSAndroid Build Coastguard Worker    %singlequote_bashisms = (
773*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN
774*49cdfc7eSAndroid Build Coastguard Worker          . qr'echo\s+(?:-[^e\s]+\s+)?\'[^\']*(\\[abcEfnrtv0])+.*?[\']' =>
775*49cdfc7eSAndroid Build Coastguard Worker          q<unsafe echo with backslash>,
776*49cdfc7eSAndroid Build Coastguard Worker        $LEADIN
777*49cdfc7eSAndroid Build Coastguard Worker          . qr'source\s+[\"\']?(?:\.\/|\/|\$|[\w~.-])\S*' =>
778*49cdfc7eSAndroid Build Coastguard Worker          q<should be '.', not 'source'>,
779*49cdfc7eSAndroid Build Coastguard Worker    );
780*49cdfc7eSAndroid Build Coastguard Worker
781*49cdfc7eSAndroid Build Coastguard Worker    if ($opt_echo) {
782*49cdfc7eSAndroid Build Coastguard Worker        $bashisms{ $LEADIN . qr'echo\s+-[A-Za-z]*n' } = q<echo -n>;
783*49cdfc7eSAndroid Build Coastguard Worker    }
784*49cdfc7eSAndroid Build Coastguard Worker    if ($opt_posix) {
785*49cdfc7eSAndroid Build Coastguard Worker        $bashisms{ $LEADIN . qr'local\s+\w+(\s+\W|\s*[;&|)]|$)' }
786*49cdfc7eSAndroid Build Coastguard Worker          = q<local foo>;
787*49cdfc7eSAndroid Build Coastguard Worker        $bashisms{ $LEADIN . qr'local\s+\w+=' }      = q<local foo=bar>;
788*49cdfc7eSAndroid Build Coastguard Worker        $bashisms{ $LEADIN . qr'local\s+\w+\s+\w+' } = q<local x y>;
789*49cdfc7eSAndroid Build Coastguard Worker        $bashisms{ $LEADIN . qr'((?:test|\[)\s+.+\s-[ao])\s' } = q<test -a/-o>;
790*49cdfc7eSAndroid Build Coastguard Worker        $bashisms{ $LEADIN . qr'kill\s+-[^sl]\w*' } = q<kill -[0-9] or -[A-Z]>;
791*49cdfc7eSAndroid Build Coastguard Worker        $bashisms{ $LEADIN . qr'trap\s+["\']?.*["\']?\s+.*[1-9]' }
792*49cdfc7eSAndroid Build Coastguard Worker          = q<trap with signal numbers>;
793*49cdfc7eSAndroid Build Coastguard Worker    }
794*49cdfc7eSAndroid Build Coastguard Worker
795*49cdfc7eSAndroid Build Coastguard Worker    if ($makefile) {
796*49cdfc7eSAndroid Build Coastguard Worker        $string_bashisms{qr'(\$\(|\`)\s*\<\s*([^\s\)]{2,}|[^DF])\s*(\)|\`)'}
797*49cdfc7eSAndroid Build Coastguard Worker          = q<'$(\< foo)' should be '$(cat foo)'>;
798*49cdfc7eSAndroid Build Coastguard Worker    } else {
799*49cdfc7eSAndroid Build Coastguard Worker        $bashisms{ $LEADIN . qr'\w+\+=' } = q<should be VAR="${VAR}foo">;
800*49cdfc7eSAndroid Build Coastguard Worker        $string_bashisms{qr'(\$\(|\`)\s*\<\s*\S+\s*(\)|\`)'}
801*49cdfc7eSAndroid Build Coastguard Worker          = q<'$(\< foo)' should be '$(cat foo)'>;
802*49cdfc7eSAndroid Build Coastguard Worker    }
803*49cdfc7eSAndroid Build Coastguard Worker
804*49cdfc7eSAndroid Build Coastguard Worker    if ($opt_extra) {
805*49cdfc7eSAndroid Build Coastguard Worker        $string_bashisms{qr'\$\{?BASH\}?\b'}            = q<$BASH>;
806*49cdfc7eSAndroid Build Coastguard Worker        $string_bashisms{qr'(?:^|\s+)RANDOM='}          = q<RANDOM=>;
807*49cdfc7eSAndroid Build Coastguard Worker        $string_bashisms{qr'(?:^|\s+)(OS|MACH)TYPE='}   = q<(OS|MACH)TYPE=>;
808*49cdfc7eSAndroid Build Coastguard Worker        $string_bashisms{qr'(?:^|\s+)HOST(TYPE|NAME)='} = q<HOST(TYPE|NAME)=>;
809*49cdfc7eSAndroid Build Coastguard Worker        $string_bashisms{qr'(?:^|\s+)DIRSTACK='}        = q<DIRSTACK=>;
810*49cdfc7eSAndroid Build Coastguard Worker        $string_bashisms{qr'(?:^|\s+)EUID='}            = q<EUID=>;
811*49cdfc7eSAndroid Build Coastguard Worker        $string_bashisms{qr'(?:^|\s+)UID='}             = q<UID=>;
812*49cdfc7eSAndroid Build Coastguard Worker        $string_bashisms{qr'(?:^|\s+)BASH(_[A-Z]+)?='}  = q<BASH(_SOMETHING)=>;
813*49cdfc7eSAndroid Build Coastguard Worker        $string_bashisms{qr'(?:^|\s+)SHELLOPTS='}       = q<SHELLOPTS=>;
814*49cdfc7eSAndroid Build Coastguard Worker        $string_bashisms{qr'\$\{?POSIXLY_CORRECT\}?\b'} = q<$POSIXLY_CORRECT>;
815*49cdfc7eSAndroid Build Coastguard Worker    }
816*49cdfc7eSAndroid Build Coastguard Worker}
817