shlint 0.1.2 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|