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