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