]> git.pld-linux.org Git - packages/checkbashisms.git/blame - checkbashisms.pl
- from altlinux src.rpm package
[packages/checkbashisms.git] / checkbashisms.pl
CommitLineData
0d697527
ER
1#! /usr/bin/perl -w
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, you can find it on the World Wide
22# Web at http://www.gnu.org/copyleft/gpl.html, or write to the Free
23# Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
24# MA 02111-1307, USA.
25
26use strict;
27use Getopt::Long;
28
29sub init_hashes;
30
31(my $progname = $0) =~ s|.*/||;
32
33my $usage = <<"EOF";
34Usage: $progname [-n] [-f] [-x] script ...
35 or: $progname --help
36 or: $progname --version
37This script performs basic checks for the presence of bashisms
38in /bin/sh scripts.
39EOF
40
41my $version = <<"EOF";
42This is $progname, from the Debian devscripts package, version ###VERSION###
43This code is copyright 2003 by Julian Gilbey <jdg\@debian.org>,
44based on original code which is copyright 1998 by Richard Braakman
45and copyright 2002 by Josip Rodin.
46This program comes with ABSOLUTELY NO WARRANTY.
47You are free to redistribute this code under the terms of the
48GNU General Public License, version 2, or (at your option) any later version.
49EOF
50
51my ($opt_echo, $opt_force, $opt_extra, $opt_posix);
52my ($opt_help, $opt_version);
53
54##
55## handle command-line options
56##
57$opt_help = 1 if int(@ARGV) == 0;
58
59GetOptions("help|h" => \$opt_help,
60 "version|v" => \$opt_version,
61 "newline|n" => \$opt_echo,
62 "force|f" => \$opt_force,
63 "extra|x" => \$opt_extra,
64 "posix|p" => \$opt_posix,
65 )
66 or die "Usage: $progname [options] filelist\nRun $progname --help for more details\n";
67
68if ($opt_help) { print $usage; exit 0; }
69if ($opt_version) { print $version; exit 0; }
70
71$opt_echo = 1 if $opt_posix;
72
73my $status = 0;
74my $makefile = 0;
75my (%bashisms, %string_bashisms, %singlequote_bashisms);
76
77my $LEADIN = qr'(?:(?:^|[`&;(|{])\s*|(?:if|then|do|while|shell)\s+)';
78init_hashes;
79
80foreach my $filename (@ARGV) {
81 my $check_lines_count = -1;
82
83 if (!$opt_force) {
84 $check_lines_count = script_is_evil_and_wrong($filename);
85 }
86
87 if ($check_lines_count == 0 or $check_lines_count == 1) {
88 warn "script $filename does not appear to be a /bin/sh script; skipping\n";
89 next;
90 }
91
92 if ($check_lines_count != -1) {
93 warn "script $filename appears to be a shell wrapper; only checking the first "
94 . "$check_lines_count lines\n";
95 }
96
97 unless (open C, '<', $filename) {
98 warn "cannot open script $filename for reading: $!\n";
99 $status |= 2;
100 next;
101 }
102
103 my $cat_string = "";
104 my $quote_string = "";
105 my $last_continued = 0;
106 my $continued = 0;
107 my $found_rules = 0;
108
109 while (<C>) {
110 next unless ($check_lines_count == -1 or $. <= $check_lines_count);
111
112 if ($. == 1) { # This should be an interpreter line
113 if (m,^\#!\s*(\S+),) {
114 my $interpreter = $1;
115
116 if ($interpreter =~ m,/make$,) {
117 init_hashes if !$makefile++;
118 $makefile = 1;
119 } else {
120 init_hashes if $makefile--;
121 $makefile = 0;
122 }
123 next if $opt_force;
124
125 if ($interpreter =~ m,/bash$,) {
126 warn "script $filename is already a bash script; skipping\n";
127 $status |= 2;
128 last; # end this file
129 }
130 elsif ($interpreter !~ m,/(sh|posh)$,) {
131### ksh/zsh?
132 warn "script $filename does not appear to be a /bin/sh script; skipping\n";
133 $status |= 2;
134 last;
135 }
136 } else {
137 warn "script $filename does not appear to have a \#! interpreter line;\nyou may get strange results\n";
138 }
139 }
140
141 chomp;
142 my $orig_line = $_;
143
144 # We want to remove end-of-line comments, so need to skip
145 # comments that appear inside balanced pairs
146 # of single or double quotes
147
148 # Remove comments in the "quoted" part of a line that starts
149 # in a quoted block? The problem is that we have no idea
150 # whether the program interpreting the block treats the
151 # quote character as part of the comment or as a quote
152 # terminator. We err on the side of caution and assume it
153 # will be treated as part of the comment.
154 # s/^(?:.*?[^\\])?$quote_string(.*)$/$1/ if $quote_string ne "";
155
156 next if m,^\s*\#,; # skip comment lines
157
158 # Remove quoted strings so we can more easily ignore comments
159 # inside them
160 s/(^|[^\\](?:\\\\)*)\'(?:\\.|[^\\\'])+\'/$1''/g;
161 s/(^|[^\\](?:\\\\)*)\"(?:\\.|[^\\\"])+\"/$1""/g;
162
163 # If the remaining string contains what looks like a comment,
164 # eat it. In either case, swap the unmodified script line
165 # back in for processing.
166 if (m/(?:^|[^[\\])[\s\&;\(\)](\#.*$)/) {
167 $_ = $orig_line;
168 s/\Q$1\E//; # eat comments
169 } else {
170 $_ = $orig_line;
171 }
172
173 if ($makefile) {
174 $last_continued = $continued;
175 if (/[^\\]\\$/) {
176 $continued = 1;
177 } else {
178 $continued = 0;
179 }
180
181 # Don't match lines that look like a rule if we're in a
182 # continuation line before the start of the rules
183 if (/^[\w%-]+:+\s.*?;?(.*)$/ and !($last_continued and !$found_rules)) {
184 $found_rules = 1;
185 $_ = $1 if $1;
186 }
187
188 last if m%^(export )?SHELL\s*:?=\s*(/bin/)?bash\s*%;
189
190 s/^\t//;
191 s/(\$){2}/$1/;
192 s/^[\s\t]*@//;
193 }
194
195 if ($cat_string ne "" and m/^\Q$cat_string\E$/) {
196 $cat_string = "";
197 next;
198 }
199 my $within_another_shell = 0;
200 if (m,(^|\s+)((/usr)?/bin/)?((b|d)?a|k|z|t?c)sh\s+-c\s*.+,) {
201 $within_another_shell = 1;
202 }
203 # if cat_string is set, we are in a HERE document and need not
204 # check for things
205 if ($cat_string eq "" and !$within_another_shell) {
206 my $found = 0;
207 my $match = '';
208 my $explanation = '';
209 my $line = $_;
210
211 # Remove "" / '' as they clearly aren't quoted strings
212 # and not considering them makes the matching easier
213 $line =~ s/(^|[^\\])(\'\')+/$1/g;
214 $line =~ s/(^|[^\\])(\"\")+/$1/g;
215
216 if ($quote_string ne "") {
217 my $otherquote = ($quote_string eq "\"" ? "\'" : "\"");
218 # Inside a quoted block
219 if ($line =~ /(?:^|^.*?[^\\])$quote_string(.*)$/) {
220 my $rest = $1;
221 my $templine = $line;
222
223 # Remove quoted strings delimited with $otherquote
224 $templine =~ s/(^|[^\\])$otherquote[^$quote_string]*?[^\\]$otherquote/$1/g;
225 # Remove quotes that are themselves quoted
226 # "a'b"
227 $templine =~ s/(^|[^\\])$otherquote.*?$quote_string.*?[^\\]$otherquote/$1/g;
228 # "\""
229 $templine =~ s/(^|[^\\])$quote_string\\$quote_string$quote_string/$1/g;
230
231 # After all that, were there still any quotes left?
232 my $count = () = $templine =~ /(^|[^\\])$quote_string/g;
233 next if $count == 0;
234
235 $count = () = $rest =~ /(^|[^\\])$quote_string/g;
236 if ($count % 2 == 0) {
237 # Quoted block ends on this line
238 # Ignore everything before the closing quote
239 $line = $rest || '';
240 $quote_string = "";
241 } else {
242 next;
243 }
244 } else {
245 # Still inside the quoted block, skip this line
246 next;
247 }
248 }
249
250 # Check even if we removed the end of a quoted block
251 # in the previous check, as a single line can end one
252 # block and begin another
253 if ($quote_string eq "") {
254 # Possible start of a quoted block
255 for my $quote ("\"", "\'") {
256 my $templine = $line;
257 my $otherquote = ($quote eq "\"" ? "\'" : "\"");
258
259 # Remove balanced quotes and their content
260 $templine =~ s/(^|[^\\\"](?:\\\\)*)\'(?:\\.|[^\\\'])+\'/$1/g;
261 $templine =~ s/(^|[^\\\'](?:\\\\)*)\"(?:\\.|[^\\\"])+\"/$1/g;
262
263 # Don't flag quotes that are themselves quoted
264 # "a'b"
265 $templine =~ s/$otherquote.*?$quote.*?$otherquote//g;
266 # "\""
267 $templine =~ s/(^|[^\\])$quote\\$quote$quote/$1/g;
268 my $count = () = $templine =~ /(^|(?!\\))$quote/g;
269
270 # If there's an odd number of non-escaped
271 # quotes in the line it's almost certainly the
272 # start of a quoted block.
273 if ($count % 2 == 1) {
274 $quote_string = $quote;
275 $line =~ s/^(.*)$quote.*$/$1/;
276 last;
277 }
278 }
279 }
280
281 # since this test is ugly, I have to do it by itself
282 # detect source (.) trying to pass args to the command it runs
283 # The first expression weeds out '. "foo bar"'
284 if (not $found and
285 not m/$LEADIN\.\s+(\"[^\"]+\"|\'[^\']+\'|\$\([^)]+\)+(?:\/[^\s;]+)?)\s*(\&|\||\d?>|<|;|\Z)/
286 and m/$LEADIN(\.\s+[^\s;\`:]+\s+([^\s;]+))/) {
287 if ($2 =~ /^(\&|\||\d?>|<)/) {
288 # everything is ok
289 ;
290 } else {
291 $found = 1;
292 $match = $1;
293 $explanation = "sourced script with arguments";
294 output_explanation($filename, $orig_line, $explanation);
295 }
296 }
297
298 # Remove "quoted quotes". They're likely to be inside
299 # another pair of quotes; we're not interested in
300 # them for their own sake and removing them makes finding
301 # the limits of the outer pair far easier.
302 $line =~ s/(^|[^\\\'\"])\"\'\"/$1/g;
303 $line =~ s/(^|[^\\\'\"])\'\"\'/$1/g;
304
305 while (my ($re,$expl) = each %singlequote_bashisms) {
306 if ($line =~ m/($re)/) {
307 $found = 1;
308 $match = $1;
309 $explanation = $expl;
310 output_explanation($filename, $orig_line, $explanation);
311 }
312 }
313
314 my $re='(?<![\$\\\])\$\'[^\']+\'';
315 if ($line =~ m/(.*)($re)/){
316 my $count = () = $1 =~ /(^|[^\\])\'/g;
317 if( $count % 2 == 0 ) {
318 output_explanation($filename, $orig_line, q<$'...' should be "$(printf '...')">);
319 }
320 }
321
322 # $cat_line contains the version of the line we'll check
323 # for heredoc delimiters later. Initially, remove any
324 # spaces between << and the delimiter to make the following
325 # updates to $cat_line easier.
326 my $cat_line = $line;
327 $cat_line =~ s/(<\<-?)\s+/$1/g;
328
329 # Ignore anything inside single quotes; it could be an
330 # argument to grep or the like.
331 $line =~ s/(^|[^\\\"](?:\\\\)*)\'(?:\\.|[^\\\'])+\'/$1''/g;
332
333 # As above, with the exception that we don't remove the string
334 # if the quote is immediately preceeded by a < or a -, so we
335 # can match "foo <<-?'xyz'" as a heredoc later
336 # The check is a little more greedy than we'd like, but the
337 # heredoc test itself will weed out any false positives
338 $cat_line =~ s/(^|[^<\\\"-](?:\\\\)*)\'(?:\\.|[^\\\'])+\'/$1''/g;
339
340 $re='(?<![\$\\\])\$\"[^\"]+\"';
341 if ($line =~ m/(.*)($re)/){
342 my $count = () = $1 =~ /(^|[^\\])\"/g;
343 if( $count % 2 == 0 ) {
344 output_explanation($filename, $orig_line, q<$"foo" should be eval_gettext "foo">);
345 }
346 }
347
348 while (my ($re,$expl) = each %string_bashisms) {
349 if ($line =~ m/($re)/) {
350 $found = 1;
351 $match = $1;
352 $explanation = $expl;
353 output_explanation($filename, $orig_line, $explanation);
354 }
355 }
356
357 # We've checked for all the things we still want to notice in
358 # double-quoted strings, so now remove those strings as well.
359 $line =~ s/(^|[^\\\'](?:\\\\)*)\"(?:\\.|[^\\\"])+\"/$1""/g;
360 $cat_line =~ s/(^|[^<\\\'-](?:\\\\)*)\"(?:\\.|[^\\\"])+\"/$1""/g;
361 while (my ($re,$expl) = each %bashisms) {
362 if ($line =~ m/($re)/) {
363 $found = 1;
364 $match = $1;
365 $explanation = $expl;
366 output_explanation($filename, $orig_line, $explanation);
367 }
368 }
369
370 # Only look for the beginning of a heredoc here, after we've
371 # stripped out quoted material, to avoid false positives.
372 if ($cat_line =~ m/(?:^|[^<])\<\<\-?\s*(?:[\\]?(\w+)|[\'\"](.*?)[\'\"])/) {
373 $cat_string = $1;
374 $cat_string = $2 if not defined $cat_string;
375 }
376 }
377 }
378
379 close C;
380}
381
382exit $status;
383
384sub output_explanation {
385 my ($filename, $line, $explanation) = @_;
386
387 warn "possible bashism in $filename line $. ($explanation):\n$line\n";
388 $status |= 1;
389}
390
391# Returns non-zero if the given file is not actually a shell script,
392# just looks like one.
393sub script_is_evil_and_wrong {
394 my ($filename) = @_;
395 my $ret = -1;
396 # lintian's version of this function aborts if the file
397 # can't be opened, but we simply return as the next
398 # test in the calling code handles reporting the error
399 # itself
400 open (IN, '<', $filename) or return $ret;
401 my $i = 0;
402 my $var = "0";
403 my $backgrounded = 0;
404 local $_;
405 while (<IN>) {
406 chomp;
407 next if /^#/o;
408 next if /^$/o;
409 last if (++$i > 55);
410 if (m~
411 # the exec should either be "eval"ed or a new statement
412 (^\s*|\beval\s*[\'\"]|(;|&&|\b(then|else))\s*)
413
414 # eat anything between the exec and $0
415 exec\s*.+\s*
416
417 # optionally quoted executable name (via $0)
418 .?\$$var.?\s*
419
420 # optional "end of options" indicator
421 (--\s*)?
422
423 # Match expressions of the form '${1+$@}', '${1:+"$@"',
424 # '"${1+$@', "$@", etc where the quotes (before the dollar
425 # sign(s)) are optional and the second (or only if the $1
426 # clause is omitted) parameter may be $@ or $*.
427 #
428 # Finally the whole subexpression may be omitted for scripts
429 # which do not pass on their parameters (i.e. after re-execing
430 # they take their parameters (and potentially data) from stdin
431 .?(\${1:?\+.?)?(\$(\@|\*))?~x) {
432 $ret = $. - 1;
433 last;
434 } elsif (/^\s*(\w+)=\$0;/) {
435 $var = $1;
436 } elsif (m~
437 # Match scripts which use "foo $0 $@ &\nexec true\n"
438 # Program name
439 \S+\s+
440
441 # As above
442 .?\$$var.?\s*
443 (--\s*)?
444 .?(\${1:?\+.?)?(\$(\@|\*))?.?\s*\&~x) {
445
446 $backgrounded = 1;
447 } elsif ($backgrounded and m~
448 # the exec should either be "eval"ed or a new statement
449 (^\s*|\beval\s*[\'\"]|(;|&&|\b(then|else))\s*)
450 exec\s+true(\s|\Z)~x) {
451
452 $ret = $. - 1;
453 last;
454 } elsif (m~\@DPATCH\@~) {
455 $ret = $. - 1;
456 last;
457 }
458
459 }
460 close IN;
461 return $ret;
462}
463
464sub init_hashes {
465
466 %bashisms = (
467 qr'(?:^|\s+)function \w+(\s|\(|\Z)' => q<'function' is useless>,
468 $LEADIN . qr'select\s+\w+' => q<'select' is not POSIX>,
469 qr'(test|-o|-a)\s*[^\s]+\s+==\s' => q<should be 'b = a'>,
470 qr'\[\s+[^\]]+\s+==\s' => q<should be 'b = a'>,
471 qr'\s\|\&' => q<pipelining is not POSIX>,
472 qr'[^\\\$]\{([^\s\\\}]*?,)+[^\\\}\s]*\}' => q<brace expansion>,
473 qr'\{\d+\.\.\d+\}' => q<brace expansion, should be $(seq a b)>,
474 qr'(?:^|\s+)\w+\[\d+\]=' => q<bash arrays, H[0]>,
475 $LEADIN . qr'read\s+(?:-[a-qs-zA-Z\d-]+)' => q<read with option other than -r>,
476 $LEADIN . qr'read\s*(?:-\w+\s*)*(?:\".*?\"|[\'].*?[\'])?\s*(?:;|$)'
477 => q<read without variable>,
478 $LEADIN . qr'echo\s+(-n\s+)?-n?en?\s' => q<echo -e>,
479 $LEADIN . qr'exec\s+-[acl]' => q<exec -c/-l/-a name>,
480 $LEADIN . qr'let\s' => q<let ...>,
481 qr'(?<![\$\(])\(\(.*\)\)' => q<'((' should be '$(('>,
482 qr'(?:^|\s+)(\[|test)\s+-a' => q<test with unary -a (should be -e)>,
483 qr'\&>' => q<should be \>word 2\>&1>,
484 qr'(<\&|>\&)\s*((-|\d+)[^\s;|)`&\\\\]|[^-\d\s]+)' =>
485 q<should be \>word 2\>&1>,
486 $LEADIN . qr'kill\s+-[^sl]\w*' => q<kill -[0-9] or -[A-Z]>,
487 $LEADIN . qr'trap\s+["\']?.*["\']?\s+.*[1-9]' => q<trap with signal numbers>,
488 qr'\[\[(?!:)' => q<alternative test command ([[ foo ]] should be [ foo ])>,
489 qr'/dev/(tcp|udp)' => q</dev/(tcp|udp)>,
490 $LEADIN . qr'builtin\s' => q<builtin>,
491 $LEADIN . qr'caller\s' => q<caller>,
492 $LEADIN . qr'compgen\s' => q<compgen>,
493 $LEADIN . qr'complete\s' => q<complete>,
494 $LEADIN . qr'declare\s' => q<declare>,
495 $LEADIN . qr'dirs(\s|\Z)' => q<dirs>,
496 $LEADIN . qr'disown\s' => q<disown>,
497 $LEADIN . qr'enable\s' => q<enable>,
498 $LEADIN . qr'mapfile\s' => q<mapfile>,
499 $LEADIN . qr'readarray\s' => q<readarray>,
500 $LEADIN . qr'shopt(\s|\Z)' => q<shopt>,
501 $LEADIN . qr'suspend\s' => q<suspend>,
502 $LEADIN . qr'time\s' => q<time>,
503 $LEADIN . qr'type\s' => q<type>,
504 $LEADIN . qr'typeset\s' => q<typeset>,
505 $LEADIN . qr'ulimit(\s|\Z)' => q<ulimit>,
506 $LEADIN . qr'set\s+-[BHT]+' => q<set -[BHT]>,
507 $LEADIN . qr'alias\s+-p' => q<alias -p>,
508 $LEADIN . qr'unalias\s+-a' => q<unalias -a>,
509 $LEADIN . qr'local\s+-[a-zA-Z]+' => q<local -opt>,
510 qr'(?:^|\s+)\s*\(?\w*[^\(\w\s]+\S*?\s*\(\)\s*([\{|\(]|\Z)'
511 => q<function names should only contain [a-z0-9_]>,
512 $LEADIN . qr'(push|pop)d(\s|\Z)' => q<(push|pop)d>,
513 $LEADIN . qr'export\s+-[^p]' => q<export only takes -p as an option>,
514 qr'(?:^|\s+)[<>]\(.*?\)' => q<\<() process substituion>,
515 $LEADIN . qr'readonly\s+-[af]' => q<readonly -[af]>,
516 $LEADIN . qr'(sh|\$\{?SHELL\}?) -[rD]' => q<sh -[rD]>,
517 $LEADIN . qr'(sh|\$\{?SHELL\}?) --\w+' => q<sh --long-option>,
518 $LEADIN . qr'(sh|\$\{?SHELL\}?) [-+]O' => q<sh [-+]O>,
519 );
520
521 %string_bashisms = (
522 qr'\$\[[^][]+\]' => q<'$[' should be '$(('>,
523 qr'\$\{\w+\:\d+(?::\d+)?\}' => q<${foo:3[:1]}>,
524 qr'\$\{!\w+[\@*]\}' => q<${!prefix[*|@]>,
525 qr'\$\{!\w+\}' => q<${!name}>,
526 qr'\$\{\w+(/.+?){1,2}\}' => q<${parm/?/pat[/str]}>,
527 qr'\$\{\#?\w+\[[0-9\*\@]+\]\}' => q<bash arrays, ${name[0|*|@]}>,
528 qr'\$\{?RANDOM\}?\b' => q<$RANDOM>,
529 qr'\$\{?(OS|MACH)TYPE\}?\b' => q<$(OS|MACH)TYPE>,
530 qr'\$\{?HOST(TYPE|NAME)\}?\b' => q<$HOST(TYPE|NAME)>,
531 qr'\$\{?DIRSTACK\}?\b' => q<$DIRSTACK>,
532 qr'\$\{?EUID\}?\b' => q<$EUID should be "$(id -u)">,
533 qr'\$\{?UID\}?\b' => q<$UID should be "$(id -ru)">,
534 qr'\$\{?SECONDS\}?\b' => q<$SECONDS>,
535 qr'\$\{?BASH_[A-Z]+\}?\b' => q<$BASH_SOMETHING>,
536 qr'\$\{?SHELLOPTS\}?\b' => q<$SHELLOPTS>,
537 qr'\$\{?PIPESTATUS\}?\b' => q<$PIPESTATUS>,
538 qr'\$\{?SHLVL\}?\b' => q<$SHLVL>,
539 qr'<<<' => q<\<\<\< here string>,
540 $LEADIN . qr'echo\s+(?:-[^e\s]+\s+)?\"[^\"]*(\\[abcEfnrtv0])+.*?[\"]' => q<unsafe echo with backslash>,
541 );
542
543 %singlequote_bashisms = (
544 $LEADIN . qr'echo\s+(?:-[^e\s]+\s+)?\'[^\']*(\\[abcEfnrtv0])+.*?[\']' => q<unsafe echo with backslash>,
545 $LEADIN . qr'source\s+[\"\']?(?:\.\/|\/|\$|[\w~.-])\S*' =>
546 q<should be '.', not 'source'>,
547 );
548
549 if ($opt_echo) {
550 $bashisms{$LEADIN . qr'echo\s+-[A-Za-z]*n'} = q<echo -n>;
551 }
552 if ($opt_posix) {
553 $bashisms{$LEADIN . qr'local\s+\w+(\s+\W|\s*[;&|)]|$)'} = q<local foo>;
554 $bashisms{$LEADIN . qr'local\s+\w+='} = q<local foo=bar>;
555 $bashisms{$LEADIN . qr'local\s+\w+\s+\w+'} = q<local x y>;
556 $bashisms{$LEADIN . qr'((?:test|\[)\s+.+\s-[ao])\s'} = q<test -a/-o>;
557 }
558
559 if ($makefile) {
560 $string_bashisms{qr'(\$\(|\`)\s*\<\s*([^\s\)]{2,}|[^DF])\s*(\)|\`)'} =
561 q<'$(\< foo)' should be '$(cat foo)'>;
562 } else {
563 $bashisms{$LEADIN . qr'\w+\+='} = q<should be VAR="${VAR}foo">;
564 $string_bashisms{qr'(\$\(|\`)\s*\<\s*\S+\s*(\)|\`)'} = q<'$(\< foo)' should be '$(cat foo)'>;
565 }
566
567 if ($opt_extra) {
568 $string_bashisms{qr'\$\{?BASH\}?\b'} = q<$BASH>;
569 $string_bashisms{qr'(?:^|\s+)RANDOM='} = q<RANDOM=>;
570 $string_bashisms{qr'(?:^|\s+)(OS|MACH)TYPE='} = q<(OS|MACH)TYPE=>;
571 $string_bashisms{qr'(?:^|\s+)HOST(TYPE|NAME)='} = q<HOST(TYPE|NAME)=>;
572 $string_bashisms{qr'(?:^|\s+)DIRSTACK='} = q<DIRSTACK=>;
573 $string_bashisms{qr'(?:^|\s+)EUID='} = q<EUID=>;
574 $string_bashisms{qr'(?:^|\s+)UID='} = q<UID=>;
575 $string_bashisms{qr'(?:^|\s+)BASH(_[A-Z]+)?='} = q<BASH(_SOMETHING)=>;
576 $string_bashisms{qr'(?:^|\s+)SHELLOPTS='} = q<SHELLOPTS=>;
577 $string_bashisms{qr'\$\{?POSIXLY_CORRECT\}?\b'} = q<$POSIXLY_CORRECT>;
578 }
579}
This page took 0.20752 seconds and 4 git commands to generate.