shlint 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/checkbashisms +129 -40
- metadata +3 -3
data/lib/checkbashisms
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
# Copyright (C) 2002 Josip Rodin
|
7
7
|
# This version is
|
8
8
|
# Copyright (C) 2003 Julian Gilbey
|
9
|
-
#
|
9
|
+
#
|
10
10
|
# This program is free software; you can redistribute it and/or modify
|
11
11
|
# it under the terms of the GNU General Public License as published by
|
12
12
|
# the Free Software Foundation; either version 2 of the License, or
|
@@ -21,7 +21,8 @@
|
|
21
21
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
22
22
|
|
23
23
|
use strict;
|
24
|
-
use Getopt::Long;
|
24
|
+
use Getopt::Long qw(:config gnu_getopt);
|
25
|
+
use File::Temp qw/tempfile/;
|
25
26
|
|
26
27
|
sub init_hashes;
|
27
28
|
|
@@ -47,6 +48,12 @@ EOF
|
|
47
48
|
|
48
49
|
my ($opt_echo, $opt_force, $opt_extra, $opt_posix);
|
49
50
|
my ($opt_help, $opt_version);
|
51
|
+
my @filenames;
|
52
|
+
|
53
|
+
# Detect if STDIN is a pipe
|
54
|
+
if (scalar(@ARGV) == 0 && (-p STDIN or -f STDIN)) {
|
55
|
+
push(@ARGV, '-');
|
56
|
+
}
|
50
57
|
|
51
58
|
##
|
52
59
|
## handle command-line options
|
@@ -77,22 +84,34 @@ init_hashes;
|
|
77
84
|
foreach my $filename (@ARGV) {
|
78
85
|
my $check_lines_count = -1;
|
79
86
|
|
87
|
+
my $display_filename = $filename;
|
88
|
+
|
89
|
+
if ($filename eq '-') {
|
90
|
+
my $tmp_fh;
|
91
|
+
($tmp_fh, $filename) = tempfile("chkbashisms_tmp.XXXX", TMPDIR => 1, UNLINK => 1);
|
92
|
+
while (my $line = <STDIN>) {
|
93
|
+
print $tmp_fh $line;
|
94
|
+
}
|
95
|
+
close($tmp_fh);
|
96
|
+
$display_filename = "(stdin)";
|
97
|
+
}
|
98
|
+
|
80
99
|
if (!$opt_force) {
|
81
100
|
$check_lines_count = script_is_evil_and_wrong($filename);
|
82
101
|
}
|
83
102
|
|
84
103
|
if ($check_lines_count == 0 or $check_lines_count == 1) {
|
85
|
-
warn "script $
|
104
|
+
warn "script $display_filename does not appear to be a /bin/sh script; skipping\n";
|
86
105
|
next;
|
87
106
|
}
|
88
107
|
|
89
108
|
if ($check_lines_count != -1) {
|
90
|
-
warn "script $
|
109
|
+
warn "script $display_filename appears to be a shell wrapper; only checking the first "
|
91
110
|
. "$check_lines_count lines\n";
|
92
111
|
}
|
93
112
|
|
94
113
|
unless (open C, '<', $filename) {
|
95
|
-
warn "cannot open script $
|
114
|
+
warn "cannot open script $display_filename for reading: $!\n";
|
96
115
|
$status |= 2;
|
97
116
|
next;
|
98
117
|
}
|
@@ -105,6 +124,7 @@ foreach my $filename (@ARGV) {
|
|
105
124
|
my $found_rules = 0;
|
106
125
|
my $buffered_orig_line = "";
|
107
126
|
my $buffered_line = "";
|
127
|
+
my %start_lines;
|
108
128
|
|
109
129
|
while (<C>) {
|
110
130
|
next unless ($check_lines_count == -1 or $. <= $check_lines_count);
|
@@ -123,18 +143,18 @@ foreach my $filename (@ARGV) {
|
|
123
143
|
next if $opt_force;
|
124
144
|
|
125
145
|
if ($interpreter =~ m,/bash$,) {
|
126
|
-
warn "script $
|
146
|
+
warn "script $display_filename is already a bash script; skipping\n";
|
127
147
|
$status |= 2;
|
128
148
|
last; # end this file
|
129
149
|
}
|
130
150
|
elsif ($interpreter !~ m,/(sh|posh)$,) {
|
131
151
|
### ksh/zsh?
|
132
|
-
warn "script $
|
152
|
+
warn "script $display_filename does not appear to be a /bin/sh script; skipping\n";
|
133
153
|
$status |= 2;
|
134
154
|
last;
|
135
155
|
}
|
136
156
|
} else {
|
137
|
-
warn "script $
|
157
|
+
warn "script $display_filename does not appear to have a \#! interpreter line;\nyou may get strange results\n";
|
138
158
|
}
|
139
159
|
}
|
140
160
|
|
@@ -163,6 +183,12 @@ foreach my $filename (@ARGV) {
|
|
163
183
|
s/(^|[^\\](?:\\\\)*)\'(?:\\.|[^\\\'])+\'/$1''/g;
|
164
184
|
s/(^|[^\\](?:\\\\)*)\"(?:\\.|[^\\\"])+\"/$1""/g;
|
165
185
|
|
186
|
+
# If inside a quoted string, remove everything before the quote
|
187
|
+
s/^.+?\'//
|
188
|
+
if ($quote_string eq "'");
|
189
|
+
s/^.+?[^\\]\"//
|
190
|
+
if ($quote_string eq '"');
|
191
|
+
|
166
192
|
# If the remaining string contains what looks like a comment,
|
167
193
|
# eat it. In either case, swap the unmodified script line
|
168
194
|
# back in for processing.
|
@@ -201,7 +227,7 @@ foreach my $filename (@ARGV) {
|
|
201
227
|
if (/^[\w%-]+:+\s.*?;?(.*)$/ and !($last_continued and !$found_rules)) {
|
202
228
|
$found_rules = 1;
|
203
229
|
$_ = $1 if $1;
|
204
|
-
}
|
230
|
+
}
|
205
231
|
|
206
232
|
last if m%^\s*(override\s|export\s)?\s*SHELL\s*:?=\s*(/bin/)?bash\s*%;
|
207
233
|
|
@@ -278,8 +304,26 @@ foreach my $filename (@ARGV) {
|
|
278
304
|
my $otherquote = ($quote eq "\"" ? "\'" : "\"");
|
279
305
|
|
280
306
|
# Remove balanced quotes and their content
|
281
|
-
|
282
|
-
|
307
|
+
while (1) {
|
308
|
+
my ($length_single, $length_double) = (0, 0);
|
309
|
+
|
310
|
+
# Determine which one would match first:
|
311
|
+
if ($templine =~ m/(^.+?(?:^|[^\\\"](?:\\\\)*)\')[^\']*\'/) {
|
312
|
+
$length_single = length($1);
|
313
|
+
}
|
314
|
+
if ($templine =~ m/(^.*?(?:^|[^\\\'](?:\\\\)*)\")(?:\\.|[^\\\"])+\"/) {
|
315
|
+
$length_double = length($1);
|
316
|
+
}
|
317
|
+
|
318
|
+
# Now simplify accordingly (shorter is preferred):
|
319
|
+
if ($length_single != 0 && ($length_single < $length_double || $length_double == 0)) {
|
320
|
+
$templine =~ s/(^|[^\\\"](?:\\\\)*)\'[^\']*\'/$1/;
|
321
|
+
} elsif ($length_double != 0) {
|
322
|
+
$templine =~ s/(^|[^\\\'](?:\\\\)*)\"(?:\\.|[^\\\"])+\"/$1/;
|
323
|
+
} else {
|
324
|
+
last;
|
325
|
+
}
|
326
|
+
}
|
283
327
|
|
284
328
|
# Don't flag quotes that are themselves quoted
|
285
329
|
# "a'b"
|
@@ -295,6 +339,7 @@ foreach my $filename (@ARGV) {
|
|
295
339
|
# start of a quoted block.
|
296
340
|
if ($count % 2 == 1) {
|
297
341
|
$quote_string = $quote;
|
342
|
+
$start_lines{'quote_string'} = $.;
|
298
343
|
$line =~ s/^(.*)$quote.*$/$1/;
|
299
344
|
last;
|
300
345
|
}
|
@@ -305,8 +350,8 @@ foreach my $filename (@ARGV) {
|
|
305
350
|
# detect source (.) trying to pass args to the command it runs
|
306
351
|
# The first expression weeds out '. "foo bar"'
|
307
352
|
if (not $found and
|
308
|
-
not m/$LEADIN\.\s+(\"[^\"]+\"|\'[^\']+\'|\$\([^)]+\)+(?:\/[^\s;]+)?)\s*(\&|\||\d?>|<|;|\Z)/
|
309
|
-
and m/$LEADIN(\.\s+[^\s;\`:]+\s+([^\s;]+))/) {
|
353
|
+
not m/$LEADIN\.\s+(\"[^\"]+\"|\'[^\']+\'|\$\([^)]+\)+(?:\/[^\s;]+)?)\s*(\&|\||\d?>|<|;|\Z)/o
|
354
|
+
and m/$LEADIN(\.\s+[^\s;\`:]+\s+([^\s;]+))/o) {
|
310
355
|
if ($2 =~ /^(\&|\||\d?>|<)/) {
|
311
356
|
# everything is ok
|
312
357
|
;
|
@@ -314,7 +359,7 @@ foreach my $filename (@ARGV) {
|
|
314
359
|
$found = 1;
|
315
360
|
$match = $1;
|
316
361
|
$explanation = "sourced script with arguments";
|
317
|
-
output_explanation($
|
362
|
+
output_explanation($display_filename, $orig_line, $explanation);
|
318
363
|
}
|
319
364
|
}
|
320
365
|
|
@@ -330,50 +375,52 @@ foreach my $filename (@ARGV) {
|
|
330
375
|
$found = 1;
|
331
376
|
$match = $1;
|
332
377
|
$explanation = $expl;
|
333
|
-
output_explanation($
|
378
|
+
output_explanation($display_filename, $orig_line, $explanation);
|
334
379
|
}
|
335
380
|
}
|
336
381
|
|
337
382
|
my $re='(?<![\$\\\])\$\'[^\']+\'';
|
338
|
-
if ($line =~ m/(.*)($re)/){
|
383
|
+
if ($line =~ m/(.*)($re)/o){
|
339
384
|
my $count = () = $1 =~ /(^|[^\\])\'/g;
|
340
385
|
if( $count % 2 == 0 ) {
|
341
|
-
output_explanation($
|
386
|
+
output_explanation($display_filename, $orig_line, q<$'...' should be "$(printf '...')">);
|
342
387
|
}
|
343
|
-
}
|
388
|
+
}
|
344
389
|
|
345
390
|
# $cat_line contains the version of the line we'll check
|
346
391
|
# for heredoc delimiters later. Initially, remove any
|
347
392
|
# spaces between << and the delimiter to make the following
|
348
|
-
# updates to $cat_line easier.
|
393
|
+
# updates to $cat_line easier. However, don't remove the
|
394
|
+
# spaces if the delimiter starts with a -, as that changes
|
395
|
+
# how the delimiter is searched.
|
349
396
|
my $cat_line = $line;
|
350
|
-
$cat_line =~ s/(<\<-?)\s
|
397
|
+
$cat_line =~ s/(<\<-?)\s+(?!-)/$1/g;
|
351
398
|
|
352
399
|
# Ignore anything inside single quotes; it could be an
|
353
400
|
# argument to grep or the like.
|
354
401
|
$line =~ s/(^|[^\\\"](?:\\\\)*)\'(?:\\.|[^\\\'])+\'/$1''/g;
|
355
402
|
|
356
403
|
# As above, with the exception that we don't remove the string
|
357
|
-
# if the quote is immediately
|
404
|
+
# if the quote is immediately preceded by a < or a -, so we
|
358
405
|
# can match "foo <<-?'xyz'" as a heredoc later
|
359
406
|
# The check is a little more greedy than we'd like, but the
|
360
407
|
# heredoc test itself will weed out any false positives
|
361
408
|
$cat_line =~ s/(^|[^<\\\"-](?:\\\\)*)\'(?:\\.|[^\\\'])+\'/$1''/g;
|
362
409
|
|
363
410
|
$re='(?<![\$\\\])\$\"[^\"]+\"';
|
364
|
-
if ($line =~ m/(.*)($re)/){
|
411
|
+
if ($line =~ m/(.*)($re)/o){
|
365
412
|
my $count = () = $1 =~ /(^|[^\\])\"/g;
|
366
413
|
if( $count % 2 == 0 ) {
|
367
|
-
output_explanation($
|
414
|
+
output_explanation($display_filename, $orig_line, q<$"foo" should be eval_gettext "foo">);
|
368
415
|
}
|
369
|
-
}
|
416
|
+
}
|
370
417
|
|
371
418
|
while (my ($re,$expl) = each %string_bashisms) {
|
372
419
|
if ($line =~ m/($re)/) {
|
373
420
|
$found = 1;
|
374
421
|
$match = $1;
|
375
422
|
$explanation = $expl;
|
376
|
-
output_explanation($
|
423
|
+
output_explanation($display_filename, $orig_line, $explanation);
|
377
424
|
}
|
378
425
|
}
|
379
426
|
|
@@ -386,25 +433,46 @@ foreach my $filename (@ARGV) {
|
|
386
433
|
$found = 1;
|
387
434
|
$match = $1;
|
388
435
|
$explanation = $expl;
|
389
|
-
output_explanation($
|
436
|
+
output_explanation($display_filename, $orig_line, $explanation);
|
390
437
|
}
|
391
438
|
}
|
439
|
+
# This check requires the value to be compared, which could
|
440
|
+
# be done in the regex itself but requires "use re 'eval'".
|
441
|
+
# So it's better done in its own
|
442
|
+
if ($line =~ m/$LEADIN((?:exit|return)\s+(\d{3,}))/o && $2 > 255) {
|
443
|
+
$explanation = 'exit|return status code greater than 255';
|
444
|
+
output_explanation($display_filename, $orig_line, $explanation);
|
445
|
+
}
|
392
446
|
|
393
447
|
# Only look for the beginning of a heredoc here, after we've
|
394
448
|
# stripped out quoted material, to avoid false positives.
|
395
|
-
if ($cat_line =~ m/(?:^|[^<])\<\<(\-?)\s*(?:[
|
449
|
+
if ($cat_line =~ m/(?:^|[^<])\<\<(\-?)\s*(?:(?!<|'|")((?:[^\s;>|]+(?:(?<=\\)[\s;>|])?)+)|[\'\"](.*?)[\'\"])/) {
|
396
450
|
$cat_indented = ($1 && $1 eq '-')? 1 : 0;
|
397
|
-
$
|
398
|
-
$cat_string = $3
|
451
|
+
my $quoted = defined($3);
|
452
|
+
$cat_string = $quoted? $3 : $2;
|
453
|
+
unless ($quoted) {
|
454
|
+
# Now strip backslashes. Keep the position of the
|
455
|
+
# last match in a variable, as s/// resets it back
|
456
|
+
# to undef, but we don't want that.
|
457
|
+
my $pos = 0;
|
458
|
+
pos($cat_string) = $pos;
|
459
|
+
while ($cat_string =~ s/\G(.*?)\\/$1/) {
|
460
|
+
# postition += length of match + the character
|
461
|
+
# that followed the backslash:
|
462
|
+
$pos += length($1)+1;
|
463
|
+
pos($cat_string) = $pos;
|
464
|
+
}
|
465
|
+
}
|
466
|
+
$start_lines{'cat_string'} = $.;
|
399
467
|
}
|
400
468
|
}
|
401
469
|
}
|
402
470
|
|
403
|
-
warn "error: $
|
471
|
+
warn "error: $display_filename: Unterminated heredoc found, EOF reached. Wanted: <$cat_string>, opened in line $start_lines{'cat_string'}\n"
|
404
472
|
if ($cat_string ne '');
|
405
|
-
warn "error: $
|
473
|
+
warn "error: $display_filename: Unterminated quoted string found, EOF reached. Wanted: <$quote_string>, opened in line $start_lines{'quote_string'}\n"
|
406
474
|
if ($quote_string ne '');
|
407
|
-
warn "error: $
|
475
|
+
warn "error: $display_filename: EOF reached while on line continuation.\n"
|
408
476
|
if ($buffered_line ne '');
|
409
477
|
|
410
478
|
close C;
|
@@ -454,8 +522,8 @@ sub script_is_evil_and_wrong {
|
|
454
522
|
# Match expressions of the form '${1+$@}', '${1:+"$@"',
|
455
523
|
# '"${1+$@', "$@", etc where the quotes (before the dollar
|
456
524
|
# sign(s)) are optional and the second (or only if the $1
|
457
|
-
# clause is omitted) parameter may be $@ or $*.
|
458
|
-
#
|
525
|
+
# clause is omitted) parameter may be $@ or $*.
|
526
|
+
#
|
459
527
|
# Finally the whole subexpression may be omitted for scripts
|
460
528
|
# which do not pass on their parameters (i.e. after re-execing
|
461
529
|
# they take their parameters (and potentially data) from stdin
|
@@ -495,13 +563,14 @@ sub script_is_evil_and_wrong {
|
|
495
563
|
sub init_hashes {
|
496
564
|
|
497
565
|
%bashisms = (
|
498
|
-
qr'(?:^|\s+)function \
|
566
|
+
qr'(?:^|\s+)function [^<>\(\)\[\]\{\};|\s]+(\s|\(|\Z)' => q<'function' is useless>,
|
499
567
|
$LEADIN . qr'select\s+\w+' => q<'select' is not POSIX>,
|
500
568
|
qr'(test|-o|-a)\s*[^\s]+\s+==\s' => q<should be 'b = a'>,
|
501
569
|
qr'\[\s+[^\]]+\s+==\s' => q<should be 'b = a'>,
|
502
570
|
qr'\s\|\&' => q<pipelining is not POSIX>,
|
503
571
|
qr'[^\\\$]\{([^\s\\\}]*?,)+[^\\\}\s]*\}' => q<brace expansion>,
|
504
|
-
qr'\{\d+\.\.\d
|
572
|
+
qr'\{\d+\.\.\d+(?:\.\.\d+)?\}' => q<brace expansion, {a..b[..c]}should be $(seq a [c] b)>,
|
573
|
+
qr'(?i)\{[a-z]\.\.[a-z](?:\.\.\d+)?\}' => q<brace expansion>,
|
505
574
|
qr'(?:^|\s+)\w+\[\d+\]=' => q<bash arrays, H[0]>,
|
506
575
|
$LEADIN . qr'read\s+(?:-[a-qs-zA-Z\d-]+)' => q<read with option other than -r>,
|
507
576
|
$LEADIN . qr'read\s*(?:-\w+\s*)*(?:\".*?\"|[\'].*?[\'])?\s*(?:;|$)'
|
@@ -536,7 +605,10 @@ sub init_hashes {
|
|
536
605
|
$LEADIN . qr'alias\s+-p' => q<alias -p>,
|
537
606
|
$LEADIN . qr'unalias\s+-a' => q<unalias -a>,
|
538
607
|
$LEADIN . qr'local\s+-[a-zA-Z]+' => q<local -opt>,
|
539
|
-
|
608
|
+
# function '=' is special-cased due to bash arrays (think of "foo=()")
|
609
|
+
qr'(?:^|\s)\s*=\s*\(\s*\)\s*([\{|\(]|\Z)'
|
610
|
+
=> q<function names should only contain [a-z0-9_]>,
|
611
|
+
qr'(?:^|\s)(?<func>function\s)?\s*(?:[^<>\(\)\[\]\{\};|\s]*[^<>\(\)\[\]\{\};|\s\w][^<>\(\)\[\]\{\};|\s]*)(?(<func>)(?=)|(?<!=))\s*(?(<func>)(?:\(\s*\))?|\(\s*\))\s*([\{|\(]|\Z)'
|
540
612
|
=> q<function names should only contain [a-z0-9_]>,
|
541
613
|
$LEADIN . qr'(push|pop)d(\s|\Z)' => q<(push|pop)d>,
|
542
614
|
$LEADIN . qr'export\s+-[^p]' => q<export only takes -p as an option>,
|
@@ -552,15 +624,25 @@ sub init_hashes {
|
|
552
624
|
$LEADIN . qr'jobs\s' => q<jobs>,
|
553
625
|
# $LEADIN . qr'jobs\s+-[^lp]\s' => q<'jobs' with option other than -l or -p>,
|
554
626
|
$LEADIN . qr'command\s+-[^p]\s' => q<'command' with option other than -p>,
|
627
|
+
$LEADIN . qr'setvar\s' => q<setvar 'foo' 'bar' should be eval 'foo="'"$bar"'"'>,
|
628
|
+
$LEADIN . qr'trap\s+["\']?.*["\']?\s+.*(?:ERR|DEBUG|RETURN)' => q<trap with ERR|DEBUG|RETURN>,
|
629
|
+
$LEADIN . qr'(?:exit|return)\s+-\d' => q<exit|return with negative status code>,
|
630
|
+
$LEADIN . qr'(?:exit|return)\s+--' => q<'exit --' should be 'exit' (idem for return)>,
|
631
|
+
$LEADIN . qr'sleep\s+(?:-|\d+(?:[.a-z]|\s+\d))' => q<sleep only takes one integer>,
|
632
|
+
$LEADIN . qr'hash(\s|\Z)' => q<hash>,
|
633
|
+
qr'(?:[:=\s])~(?:[+-]|[+-]?\d+)(?:[/\s]|\Z)' => q<non-standard tilde expansion>,
|
555
634
|
);
|
556
635
|
|
557
636
|
%string_bashisms = (
|
558
637
|
qr'\$\[[^][]+\]' => q<'$[' should be '$(('>,
|
559
|
-
qr'\$\{
|
638
|
+
qr'\$\{(?:\w+|@|\*)\:(?:\d+|\$\{?\w+\}?)+(?::(?:\d+|\$\{?\w+\}?)+)?\}' => q<${foo:3[:1]}>,
|
560
639
|
qr'\$\{!\w+[\@*]\}' => q<${!prefix[*|@]>,
|
561
640
|
qr'\$\{!\w+\}' => q<${!name}>,
|
562
|
-
qr'\$\{
|
563
|
-
qr'\$\{
|
641
|
+
qr'\$\{(?:\w+|@|\*)([,^]{1,2}.*?)\}' => q<${parm,[,][pat]} or ${parm^[^][pat]}>,
|
642
|
+
qr'\$\{[@*]([#%]{1,2}.*?)\}' => q<${[@|*]#[#]pat} or ${[@|*]%[%]pat}>,
|
643
|
+
qr'\$\{#[@*]\}' => q<${#@} or ${#*}>,
|
644
|
+
qr'\$\{(?:\w+|@|\*)(/.+?){1,2}\}' => q<${parm/?/pat[/str]}>,
|
645
|
+
qr'\$\{\#?\w+\[.+\](?:[/,:#%^].+?)?\}' => q<bash arrays, ${name[0|*|@]}>,
|
564
646
|
qr'\$\{?RANDOM\}?\b' => q<$RANDOM>,
|
565
647
|
qr'\$\{?(OS|MACH)TYPE\}?\b' => q<$(OS|MACH)TYPE>,
|
566
648
|
qr'\$\{?HOST(TYPE|NAME)\}?\b' => q<$HOST(TYPE|NAME)>,
|
@@ -572,6 +654,13 @@ sub init_hashes {
|
|
572
654
|
qr'\$\{?SHELLOPTS\}?\b' => q<$SHELLOPTS>,
|
573
655
|
qr'\$\{?PIPESTATUS\}?\b' => q<$PIPESTATUS>,
|
574
656
|
qr'\$\{?SHLVL\}?\b' => q<$SHLVL>,
|
657
|
+
qr'\$\{?FUNCNAME\}?\b' => q<$FUNCNAME>,
|
658
|
+
qr'\$\{?TMOUT\}?\b' => q<$TMOUT>,
|
659
|
+
qr'(?:^|\s+)TMOUT=' => q<TMOUT=>,
|
660
|
+
qr'\$\{?TIMEFORMAT\}?\b' => q<$TIMEFORMAT>,
|
661
|
+
qr'(?:^|\s+)TIMEFORMAT=' => q<TIMEFORMAT=>,
|
662
|
+
qr'\$\{?_\}?\b' => q<$_>,
|
663
|
+
qr'(?:^|\s+)GLOBIGNORE=' => q<GLOBIGNORE=>,
|
575
664
|
qr'<<<' => q<\<\<\< here string>,
|
576
665
|
$LEADIN . qr'echo\s+(?:-[^e\s]+\s+)?\"[^\"]*(\\[abcEfnrtv0])+.*?[\"]' => q<unsafe echo with backslash>,
|
577
666
|
qr'\$\(\([\s\w$*/+-]*\w\+\+.*?\)\)' => q<'$((n++))' should be '$n; $((n=n+1))'>,
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shlint
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-11-16 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Checks the syntax of your shellscript against known and available shells.
|
15
15
|
email:
|
@@ -46,7 +46,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
46
46
|
version: 1.3.6
|
47
47
|
requirements: []
|
48
48
|
rubyforge_project:
|
49
|
-
rubygems_version: 1.8.
|
49
|
+
rubygems_version: 1.8.23
|
50
50
|
signing_key:
|
51
51
|
specification_version: 3
|
52
52
|
summary: A linting tool for shell.
|