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