line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#! /usr/local/bin/perl -w |
2
|
|
|
|
|
|
|
package Psh::Parser; |
3
|
|
|
|
|
|
|
|
4
|
1
|
|
|
1
|
|
4
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
1652
|
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
require Psh::OS; |
7
|
|
|
|
|
|
|
require Psh::Util; |
8
|
|
|
|
|
|
|
require Psh::Strategy; |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
sub T_END() { 0; } |
11
|
|
|
|
|
|
|
sub T_WORD() { 1; } |
12
|
|
|
|
|
|
|
sub T_PIPE() { 2; } |
13
|
|
|
|
|
|
|
sub T_REDIRECT() { 3; } |
14
|
|
|
|
|
|
|
sub T_BACKGROUND() { 4; } |
15
|
|
|
|
|
|
|
sub T_OR() { 5; } |
16
|
|
|
|
|
|
|
sub T_AND() { 6; } |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
sub T_EXECUTE() { 1; } |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
# ugly, ugly, but makes things faster |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
my %quotehash = qw|' ' " " q( ) qw( ) qq( ) ` `|; |
23
|
|
|
|
|
|
|
my %quotedquotes = (); |
24
|
|
|
|
|
|
|
my $def_quoteexp; |
25
|
|
|
|
|
|
|
my $def_tokenizer= '(\\s+|\\|\\||\\&\\&|\||=>|->|;;|;|\\&|>>|>|<<|<|\\(|\\)|\\{|\\}|\\[|\\])'; |
26
|
|
|
|
|
|
|
my $nevermatches = "(?!a)a"; |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
$def_quoteexp = $nevermatches; |
30
|
|
|
|
|
|
|
foreach my $opener (keys %quotehash) { |
31
|
|
|
|
|
|
|
$def_quoteexp .= '|' . quotemeta($opener); |
32
|
|
|
|
|
|
|
$quotedquotes{$opener} = quotemeta($quotehash{$opener}); |
33
|
|
|
|
|
|
|
} |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
my $stdallinall= "^((?:[^\\\\]|\\\\.)*?)(?:$def_tokenizer|($def_quoteexp))(.*)\$"; |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
if ($]>=5.005) { |
38
|
|
|
|
|
|
|
eval { |
39
|
|
|
|
|
|
|
$stdallinall= qr{$stdallinall}s; |
40
|
|
|
|
|
|
|
}; |
41
|
|
|
|
|
|
|
} |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
sub decompose { |
44
|
0
|
|
|
0
|
0
|
|
my ($delimexp,$line,$num,$keep,$unmatched) = @_; |
45
|
0
|
|
|
|
|
|
my @matches; |
46
|
|
|
|
|
|
|
|
47
|
0
|
0
|
|
|
|
|
if (!defined($delimexp)) { $delimexp = $def_tokenizer; } |
|
0
|
0
|
|
|
|
|
|
48
|
0
|
|
|
|
|
|
elsif ($delimexp eq ' ') { $delimexp='(\s+)'; } |
49
|
|
|
|
|
|
|
|
50
|
0
|
0
|
|
|
|
|
if (!defined($num)) { $num = -1; } |
|
0
|
|
|
|
|
|
|
51
|
0
|
0
|
|
|
|
|
if (!defined($keep)) { $keep = 1; } |
|
0
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
# Remember if delimexp came with any parenthesized subexpr, and |
54
|
|
|
|
|
|
|
# arrange for it to have exactly one so we know what each piece in |
55
|
|
|
|
|
|
|
# the match below means: |
56
|
|
|
|
|
|
|
|
57
|
0
|
|
|
|
|
|
my $saveDelimiters = 0; |
58
|
0
|
|
|
|
|
|
@matches = ('x' =~ m/$delimexp|(.)/); |
59
|
0
|
0
|
|
|
|
|
if (@matches > 2) { |
60
|
0
|
|
|
|
|
|
require Carp; |
61
|
0
|
|
|
|
|
|
Carp::carp("Delimiter regexp '$delimexp' in decompose may " . |
62
|
|
|
|
|
|
|
"contain at most 1 ()."); |
63
|
0
|
|
|
|
|
|
return undef; |
64
|
|
|
|
|
|
|
} |
65
|
0
|
0
|
|
|
|
|
if (@matches == 2) { |
66
|
0
|
|
|
|
|
|
$saveDelimiters = 1; |
67
|
|
|
|
|
|
|
} else { |
68
|
0
|
|
|
|
|
|
$delimexp = "($delimexp)"; |
69
|
|
|
|
|
|
|
} |
70
|
|
|
|
|
|
|
|
71
|
0
|
|
|
|
|
|
return _decompose($line, "^((?:[^\\\\]|\\\\.)*?)(?:$delimexp|($def_quoteexp))(.*)\$", $keep, $num, $unmatched, $saveDelimiters-1); |
72
|
|
|
|
|
|
|
} |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
sub _decompose |
75
|
|
|
|
|
|
|
{ |
76
|
0
|
|
|
0
|
|
|
my ( $line, $regexp, $keep, $num, $unmatched, $saveDelimiters)= @_; |
77
|
|
|
|
|
|
|
|
78
|
0
|
|
|
|
|
|
$saveDelimiters++; |
79
|
0
|
|
|
|
|
|
my @pieces = (''); |
80
|
0
|
|
|
|
|
|
my $startNewPiece = 0; |
81
|
0
|
|
|
|
|
|
my $freshPiece = 1; |
82
|
0
|
|
|
|
|
|
my $uquote = 0; |
83
|
0
|
|
|
|
|
|
while ($line) { |
84
|
0
|
0
|
|
|
|
|
if ($startNewPiece) { |
85
|
0
|
|
|
|
|
|
push @pieces, ''; |
86
|
0
|
|
|
|
|
|
$startNewPiece = 0; |
87
|
0
|
|
|
|
|
|
$freshPiece = 1; |
88
|
|
|
|
|
|
|
} |
89
|
0
|
0
|
|
|
|
|
if (@pieces == $num) { last; } |
|
0
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
# $delimexp is unparenthesized below because we have |
92
|
|
|
|
|
|
|
# already arranged for it to contain exactly one backref () |
93
|
0
|
|
|
|
|
|
my ($prefix,$delimiter,$quote,$rest) = |
94
|
|
|
|
|
|
|
($line =~ m/$regexp/s); |
95
|
0
|
0
|
0
|
|
|
|
if (!$keep and defined($prefix)) { |
96
|
0
|
|
|
|
|
|
$prefix= remove_backslash($prefix); |
97
|
|
|
|
|
|
|
} |
98
|
0
|
0
|
|
|
|
|
if (defined($delimiter)) { |
|
|
0
|
|
|
|
|
|
99
|
0
|
|
|
|
|
|
$pieces[$#pieces] .= $prefix; |
100
|
0
|
0
|
0
|
|
|
|
if ($saveDelimiters) { |
|
|
0
|
|
|
|
|
|
101
|
0
|
0
|
0
|
|
|
|
if (length($pieces[$#pieces]) or !$freshPiece) { |
102
|
0
|
|
|
|
|
|
push @pieces, $delimiter; |
103
|
|
|
|
|
|
|
} else { |
104
|
0
|
|
|
|
|
|
$pieces[$#pieces] = $delimiter; |
105
|
|
|
|
|
|
|
} |
106
|
0
|
|
|
|
|
|
$startNewPiece = 1; |
107
|
|
|
|
|
|
|
} elsif (@pieces > 1 or $pieces[0]) { |
108
|
0
|
|
|
|
|
|
$startNewPiece = 1; |
109
|
|
|
|
|
|
|
} |
110
|
0
|
|
|
|
|
|
$line = $rest; |
111
|
|
|
|
|
|
|
} elsif (defined($quote)) { |
112
|
0
|
|
|
|
|
|
my ($restOfQuote,$remainder) = |
113
|
|
|
|
|
|
|
($rest =~ m/^((?:[^\\]|\\.)*?)$quotedquotes{$quote}(.*)$/s); |
114
|
0
|
0
|
|
|
|
|
if (defined($restOfQuote)) { |
115
|
0
|
0
|
0
|
|
|
|
if (!$keep and |
|
|
|
0
|
|
|
|
|
116
|
|
|
|
|
|
|
$quote ne "\'" and $quote ne 'q(') { |
117
|
0
|
|
|
|
|
|
$restOfQuote= remove_backslash($restOfQuote); |
118
|
|
|
|
|
|
|
} |
119
|
0
|
|
|
|
|
|
$pieces[$#pieces]= join('',$pieces[$#pieces],$prefix, |
120
|
|
|
|
|
|
|
$quote,$restOfQuote, |
121
|
|
|
|
|
|
|
$quotehash{$quote}); |
122
|
0
|
|
|
|
|
|
$line = $remainder; |
123
|
0
|
|
|
|
|
|
$freshPiece = 0; |
124
|
|
|
|
|
|
|
} else { # can't find matching quote, give up |
125
|
0
|
|
|
|
|
|
$uquote = 1; |
126
|
0
|
|
|
|
|
|
last; |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
} else { # nothing found, so remainder all one unquoted piece |
129
|
0
|
0
|
0
|
|
|
|
if (!$keep and length($line)) { |
130
|
0
|
|
|
|
|
|
$line= remove_backslash($line); |
131
|
|
|
|
|
|
|
} |
132
|
0
|
|
|
|
|
|
last; |
133
|
|
|
|
|
|
|
} |
134
|
|
|
|
|
|
|
} |
135
|
0
|
0
|
|
|
|
|
if (length($line)) { $pieces[$#pieces] .= $line; } |
|
0
|
|
|
|
|
|
|
136
|
0
|
0
|
|
|
|
|
if (defined($unmatched)) { ${$unmatched} = $uquote; } |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
137
|
0
|
0
|
|
|
|
|
return wantarray?@pieces:\@pieces; |
138
|
|
|
|
|
|
|
} |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
sub incomplete_expr |
141
|
|
|
|
|
|
|
{ |
142
|
0
|
|
|
0
|
0
|
|
my ($line) = @_; |
143
|
0
|
0
|
|
|
|
|
return 0 unless $line=~/[\[{('"]/s; |
144
|
|
|
|
|
|
|
|
145
|
0
|
|
|
|
|
|
my $unmatch = 0; |
146
|
0
|
|
|
|
|
|
my @words= @{scalar(_decompose($line,$stdallinall, 1, undef, \$unmatch))}; |
|
0
|
|
|
|
|
|
|
147
|
0
|
0
|
|
|
|
|
if ($unmatch) { return 2; } |
|
0
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
|
149
|
0
|
|
|
|
|
|
my @openstack = (':'); # : is used as a bottom marker here |
150
|
0
|
|
|
|
|
|
my %open_of_close = qw|) ( } { ] [ " '|; |
151
|
|
|
|
|
|
|
|
152
|
0
|
|
|
|
|
|
foreach my $word (@words) { |
153
|
0
|
0
|
|
|
|
|
next if length($word)!=1; |
154
|
0
|
0
|
0
|
|
|
|
if ($word eq '[' or $word eq '{' or $word eq '(' or $word eq '"' or |
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
155
|
|
|
|
|
|
|
$word eq "\"") { |
156
|
0
|
|
|
|
|
|
push @openstack, $word; |
157
|
|
|
|
|
|
|
} elsif ($word eq ')' or $word eq '}' or $word eq ']' or $word eq '"' or |
158
|
|
|
|
|
|
|
$word eq "\"") { |
159
|
0
|
|
|
|
|
|
my $open= $open_of_close{$word}; |
160
|
0
|
|
|
|
|
|
my $curopen = pop @openstack; |
161
|
0
|
0
|
|
|
|
|
if ($open ne $curopen) { |
162
|
0
|
|
|
|
|
|
return -1; |
163
|
|
|
|
|
|
|
} |
164
|
|
|
|
|
|
|
} |
165
|
|
|
|
|
|
|
} |
166
|
0
|
0
|
|
|
|
|
if (scalar(@openstack) > 1) { return 1; } |
|
0
|
|
|
|
|
|
|
167
|
0
|
|
|
|
|
|
return 0; |
168
|
|
|
|
|
|
|
} |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
# |
171
|
|
|
|
|
|
|
# glob_expansion() |
172
|
|
|
|
|
|
|
# |
173
|
|
|
|
|
|
|
# LINE EXPANSIONS: |
174
|
|
|
|
|
|
|
# |
175
|
|
|
|
|
|
|
# If we're going to be a shell, let's act like a shell. The idea here |
176
|
|
|
|
|
|
|
# is to provide expansion functions that individual evaluation |
177
|
|
|
|
|
|
|
# strategies can use on the argument list to perform operations |
178
|
|
|
|
|
|
|
# similar to the ones a shell argument list undergoes. Each of these |
179
|
|
|
|
|
|
|
# functions should take a reference to an array of "words" and return |
180
|
|
|
|
|
|
|
# a solid (to be conservative, as opposed to modifying in place) array of |
181
|
|
|
|
|
|
|
# "expanded words". |
182
|
|
|
|
|
|
|
# |
183
|
|
|
|
|
|
|
# Bash defines eight types of expansion in its manpage: brace |
184
|
|
|
|
|
|
|
# expansion, tilde expansion, parameter and variable expansion, |
185
|
|
|
|
|
|
|
# command substitution, arithmetic expansion, word splitting, |
186
|
|
|
|
|
|
|
# pathname expansion, and process expansion. |
187
|
|
|
|
|
|
|
# |
188
|
|
|
|
|
|
|
# Of these, arithmetic expansion makes no sense in Perl. Word |
189
|
|
|
|
|
|
|
# splitting should happen "on the fly", i.e., the array returned by |
190
|
|
|
|
|
|
|
# one of these functions might have more elements than the argument |
191
|
|
|
|
|
|
|
# did. Since the perl builtin "glob" handles brace, tilde and pathname |
192
|
|
|
|
|
|
|
# expansion, here's a glob_expansion function that covers all of |
193
|
|
|
|
|
|
|
# those. Also a variable_expansion function that handles substituting |
194
|
|
|
|
|
|
|
# in the values of Perl variables. That leaves only: |
195
|
|
|
|
|
|
|
# |
196
|
|
|
|
|
|
|
# TODO: command_expansion (i.e., backticks. For this, |
197
|
|
|
|
|
|
|
# backticks would have to be added to decompose as a recognized quote |
198
|
|
|
|
|
|
|
# character), process_expansion |
199
|
|
|
|
|
|
|
# |
200
|
|
|
|
|
|
|
# TODO: should some of these line-processing actions happen in a |
201
|
|
|
|
|
|
|
# uniform way, or should things simply be left to each evaluation strategy |
202
|
|
|
|
|
|
|
# as psh currently works? |
203
|
|
|
|
|
|
|
# |
204
|
|
|
|
|
|
|
# array glob_expansion (arrayref WORDS) |
205
|
|
|
|
|
|
|
# |
206
|
|
|
|
|
|
|
# For each element x of the array referred to by WORDS, such that x |
207
|
|
|
|
|
|
|
# is not quoted, push glob(x) onto an array, and return the collected array. |
208
|
|
|
|
|
|
|
# |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
sub glob_expansion |
211
|
|
|
|
|
|
|
{ |
212
|
0
|
|
|
0
|
0
|
|
my $arref= shift; |
213
|
0
|
|
|
|
|
|
my $join_char= shift; |
214
|
0
|
|
|
|
|
|
my @retval = (); |
215
|
|
|
|
|
|
|
|
216
|
0
|
|
|
|
|
|
for my $word (@{$arref}) { |
|
0
|
|
|
|
|
|
|
217
|
0
|
0
|
0
|
|
|
|
if ($word =~ m/['"']/ # if it contains quotes |
218
|
|
|
|
|
|
|
or ($word !~ m/{.*}|\[.*\]|[*?~]/)) { # or no globbing characters |
219
|
0
|
|
|
|
|
|
push @retval, $word; # don't try to glob it |
220
|
|
|
|
|
|
|
} else { |
221
|
|
|
|
|
|
|
# Glob it. If anything happens, quote the |
222
|
|
|
|
|
|
|
# results so they won't be clobbbered later. |
223
|
0
|
|
|
|
|
|
my @results = Psh::OS::glob($word); |
224
|
0
|
0
|
0
|
|
|
|
if (scalar(@results) == 0) { |
|
|
0
|
|
|
|
|
|
225
|
0
|
|
|
|
|
|
@results = ($word); |
226
|
|
|
|
|
|
|
} elsif (scalar(@results)>1 or $results[0] ne $word) { |
227
|
0
|
|
|
|
|
|
foreach (@results) { $_ = "'$_'"; } |
|
0
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
} |
229
|
0
|
0
|
|
|
|
|
if( $join_char) { |
230
|
0
|
|
|
|
|
|
push @retval, join($join_char, @results); |
231
|
|
|
|
|
|
|
} else { |
232
|
0
|
|
|
|
|
|
push @retval, @results; |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
} |
236
|
|
|
|
|
|
|
|
237
|
0
|
|
|
|
|
|
return @retval; |
238
|
|
|
|
|
|
|
} |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
sub unquote { |
241
|
0
|
|
|
0
|
0
|
|
my $text= shift; |
242
|
|
|
|
|
|
|
|
243
|
0
|
0
|
0
|
|
|
|
if (substr($text,0,1) eq '\'' and |
|
|
0
|
0
|
|
|
|
|
|
|
0
|
|
|
|
|
|
244
|
|
|
|
|
|
|
substr($text,-1,1) eq '\'') { |
245
|
0
|
|
|
|
|
|
$text= substr($text,1,-1); |
246
|
|
|
|
|
|
|
} elsif ( substr($text,0,1) eq "\"" and |
247
|
|
|
|
|
|
|
substr($text,-1,1) eq "\"") { |
248
|
0
|
|
|
|
|
|
$text= substr($text,1,-1); |
249
|
|
|
|
|
|
|
} elsif (substr($text,0,1) eq "\\") { |
250
|
0
|
|
|
|
|
|
$text= substr($text,1); |
251
|
|
|
|
|
|
|
} |
252
|
0
|
|
|
|
|
|
return $text; |
253
|
|
|
|
|
|
|
} |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
sub remove_backslash { |
256
|
0
|
|
|
0
|
0
|
|
my $text= shift; |
257
|
|
|
|
|
|
|
|
258
|
0
|
|
|
|
|
|
$text=~ s/\\t/\t/g; |
259
|
0
|
|
|
|
|
|
$text=~ s/\\n/\n/g; |
260
|
0
|
|
|
|
|
|
$text=~ s/\\r/\r/g; |
261
|
0
|
|
|
|
|
|
$text=~ s/\\f/\f/g; |
262
|
0
|
|
|
|
|
|
$text=~ s/\\b/\b/g; |
263
|
0
|
|
|
|
|
|
$text=~ s/\\a/\a/g; |
264
|
0
|
|
|
|
|
|
$text=~ s/\\e/\e/g; |
265
|
0
|
|
|
|
|
|
$text=~ s/\\(0[0-7][0-7])/chr(oct($1))/ge; |
|
0
|
|
|
|
|
|
|
266
|
0
|
|
|
|
|
|
$text=~ s/\\(x[0-9a-fA-F][0-9a-fA-F])/chr(oct($1))/ge; |
|
0
|
|
|
|
|
|
|
267
|
0
|
|
|
|
|
|
$text=~ s/\\(.)/$1/g; |
268
|
0
|
|
|
|
|
|
return $text; |
269
|
|
|
|
|
|
|
} |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
sub ungroup { |
272
|
0
|
|
|
0
|
0
|
|
my $text= shift; |
273
|
0
|
0
|
0
|
|
|
|
if (substr($text,0,1) eq '(' and |
|
|
0
|
0
|
|
|
|
|
274
|
|
|
|
|
|
|
substr($text,-1,1) eq ')') { |
275
|
0
|
|
|
|
|
|
return substr($text,1,-1); |
276
|
|
|
|
|
|
|
} elsif (substr($text,0,1) eq '{' and |
277
|
|
|
|
|
|
|
substr($text,-1,1) eq '}') { |
278
|
0
|
|
|
|
|
|
return substr($text,1,-1); |
279
|
|
|
|
|
|
|
} |
280
|
0
|
|
|
|
|
|
return $text; |
281
|
|
|
|
|
|
|
} |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
sub parse_fileno { |
284
|
0
|
|
|
0
|
0
|
|
my $tmp= shift; |
285
|
0
|
|
|
|
|
|
my $default1= shift; |
286
|
0
|
|
|
|
|
|
my $default2= shift; |
287
|
|
|
|
|
|
|
|
288
|
0
|
|
|
|
|
|
my @tmp= split('=', $tmp); # [out=in] - not supported fully yet |
289
|
0
|
0
|
|
|
|
|
if (@tmp>2) { |
290
|
0
|
|
|
|
|
|
return undef; |
291
|
|
|
|
|
|
|
} |
292
|
0
|
0
|
|
|
|
|
if (@tmp<2) { |
293
|
0
|
|
|
|
|
|
push @tmp, $default2; |
294
|
|
|
|
|
|
|
} |
295
|
0
|
0
|
0
|
|
|
|
if (@tmp==2 && !$tmp[0]) { |
296
|
0
|
|
|
|
|
|
$tmp[0]= $default1; |
297
|
|
|
|
|
|
|
} |
298
|
0
|
|
|
|
|
|
my @result=(); |
299
|
0
|
|
|
|
|
|
foreach (@tmp) { |
300
|
1
|
|
|
1
|
|
7
|
no strict 'refs'; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
2726
|
|
301
|
0
|
0
|
|
|
|
|
if (lc($_) eq 'all') { |
302
|
0
|
|
|
|
|
|
$_=1; |
303
|
|
|
|
|
|
|
} |
304
|
0
|
0
|
|
|
|
|
if (/^\d+$/) { |
305
|
0
|
|
|
|
|
|
push @result, $_+0; |
306
|
|
|
|
|
|
|
} else { |
307
|
0
|
0
|
|
|
|
|
if (ref *{"$Psh::PerlEval::current_package\:\:$_"}{FILEHANDLE}) { |
|
0
|
|
|
|
|
|
|
308
|
0
|
|
|
|
|
|
push @result, fileno(*{"$Psh::PerlEval::current_package\:\:$_"}); |
|
0
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
} |
310
|
|
|
|
|
|
|
} |
311
|
|
|
|
|
|
|
} |
312
|
0
|
|
|
|
|
|
return @result; |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
sub make_tokens { |
316
|
0
|
|
|
0
|
0
|
|
my $line= shift; |
317
|
0
|
|
|
|
|
|
my $splitonly= shift; |
318
|
0
|
|
|
|
|
|
my @tmpparts= @{scalar(_decompose($line,$stdallinall, 0))}; |
|
0
|
|
|
|
|
|
|
319
|
0
|
0
|
|
|
|
|
return @tmpparts if $splitonly; |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
# Walk through parts and combine parenthesized parts properly |
322
|
0
|
|
|
|
|
|
my @parts=(); |
323
|
0
|
|
|
|
|
|
my $nestlevel=0; |
324
|
0
|
|
|
|
|
|
my @tmp=(); |
325
|
0
|
|
|
|
|
|
foreach (@tmpparts) { |
326
|
0
|
0
|
|
|
|
|
if (length($_)==1) { |
327
|
0
|
0
|
0
|
|
|
|
if ($_ eq '[' or $_ eq '(' or $_ eq '{') { |
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
328
|
0
|
|
|
|
|
|
$nestlevel++; |
329
|
|
|
|
|
|
|
} elsif ($_ eq '}' or $_ eq ')' or $_ eq ']') { |
330
|
0
|
|
|
|
|
|
$nestlevel--; |
331
|
|
|
|
|
|
|
} |
332
|
|
|
|
|
|
|
} |
333
|
0
|
0
|
|
|
|
|
if ($nestlevel) { |
|
|
0
|
|
|
|
|
|
334
|
0
|
|
|
|
|
|
push @tmp, $_; |
335
|
|
|
|
|
|
|
} elsif (@tmp) { |
336
|
0
|
|
|
|
|
|
push @parts,join('',@tmp,$_); |
337
|
0
|
|
|
|
|
|
@tmp=(); |
338
|
|
|
|
|
|
|
} else { |
339
|
0
|
|
|
|
|
|
push @parts, $_; |
340
|
|
|
|
|
|
|
} |
341
|
|
|
|
|
|
|
} |
342
|
|
|
|
|
|
|
|
343
|
0
|
|
|
|
|
|
my @tokens= (); |
344
|
0
|
|
|
|
|
|
my @t=(); |
345
|
0
|
|
|
|
|
|
my $tmp; |
346
|
0
|
|
|
|
|
|
while( defined($tmp= shift @parts)) { |
347
|
0
|
0
|
0
|
|
|
|
if ($tmp eq '||' or $tmp eq '&&') { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
348
|
0
|
|
|
|
|
|
push @t, @tokens; |
349
|
0
|
0
|
|
|
|
|
push @t, [T_END],[$tmp eq '||'?T_OR:T_AND]; |
350
|
0
|
|
|
|
|
|
@tokens=(); |
351
|
|
|
|
|
|
|
} |
352
|
|
|
|
|
|
|
elsif ($tmp eq ';;') { |
353
|
0
|
|
|
|
|
|
push @tokens, [T_WORD,';']; |
354
|
|
|
|
|
|
|
} |
355
|
|
|
|
|
|
|
elsif( $tmp eq '|') { |
356
|
0
|
|
|
|
|
|
my @fileno=(1,0); |
357
|
0
|
0
|
|
|
|
|
if (@parts>0) { |
358
|
0
|
|
|
|
|
|
my $tmp= shift @parts; |
359
|
0
|
0
|
|
|
|
|
if ($tmp=~/^\[(.+?)\]$/) { |
360
|
0
|
|
|
|
|
|
my $tmp2= $1; |
361
|
0
|
0
|
|
|
|
|
if (lc($tmp2) eq 'all') { |
362
|
0
|
|
|
|
|
|
push @tokens, [T_REDIRECT, '>&', 2, 1]; |
363
|
|
|
|
|
|
|
} |
364
|
0
|
|
|
|
|
|
@fileno= parse_fileno($tmp2,1,0); |
365
|
0
|
0
|
|
|
|
|
if (!@fileno) { |
366
|
0
|
|
|
|
|
|
print STDERR "Illegal syntax\n"; ## FIXME |
367
|
0
|
|
|
|
|
|
return undef; |
368
|
|
|
|
|
|
|
} |
369
|
|
|
|
|
|
|
} else { |
370
|
0
|
|
|
|
|
|
unshift @parts, $tmp; |
371
|
|
|
|
|
|
|
} |
372
|
|
|
|
|
|
|
} |
373
|
0
|
|
|
|
|
|
push @t, [T_REDIRECT, '>&', $fileno[0], 'chainout']; # needs to come first |
374
|
0
|
|
|
|
|
|
push @t, @tokens; |
375
|
0
|
|
|
|
|
|
push @t, [T_PIPE]; |
376
|
0
|
|
|
|
|
|
@tokens=( [T_REDIRECT, '<&', $fileno[1], 'chainin']); |
377
|
|
|
|
|
|
|
} elsif( $tmp =~ /^(>>?)$/) { |
378
|
0
|
|
|
|
|
|
my $tmp= $1; |
379
|
|
|
|
|
|
|
|
380
|
0
|
|
|
|
|
|
my $file; |
381
|
0
|
|
|
|
|
|
my @fileno=(1,0); |
382
|
0
|
|
|
|
|
|
my $allflag=0; |
383
|
0
|
0
|
|
|
|
|
if (@parts>0) { |
384
|
0
|
|
|
|
|
|
my $tmp= shift @parts; |
385
|
0
|
0
|
|
|
|
|
if ($tmp=~/^\[(.+?)\]$/) { |
386
|
0
|
|
|
|
|
|
my $tmp2= $1; |
387
|
0
|
0
|
|
|
|
|
if (lc($tmp2) eq 'all') { |
388
|
0
|
|
|
|
|
|
$allflag=1; |
389
|
|
|
|
|
|
|
} |
390
|
0
|
|
|
|
|
|
@fileno= parse_fileno($tmp2,1,0); |
391
|
0
|
0
|
|
|
|
|
if (!@fileno) { |
392
|
0
|
|
|
|
|
|
print STDERR "Illegal syntax\n"; ## FIXME |
393
|
0
|
|
|
|
|
|
return undef; |
394
|
|
|
|
|
|
|
} |
395
|
|
|
|
|
|
|
} else { |
396
|
0
|
|
|
|
|
|
unshift @parts, $tmp; |
397
|
|
|
|
|
|
|
} |
398
|
|
|
|
|
|
|
} |
399
|
0
|
0
|
|
|
|
|
if ($fileno[1]==0) { |
400
|
0
|
|
|
|
|
|
while( @parts>0) { |
401
|
0
|
|
|
|
|
|
$file= shift @parts; |
402
|
0
|
0
|
|
|
|
|
last if( $file !~ /^\s+$/); |
403
|
0
|
|
|
|
|
|
$file=''; |
404
|
|
|
|
|
|
|
} |
405
|
0
|
0
|
0
|
|
|
|
if( !$file or substr($file,0,1) eq '&') { |
406
|
0
|
|
|
|
|
|
Psh::Util::print_error_i18n('redirect_file_missing', |
407
|
|
|
|
|
|
|
$tmp,$Psh::bin); |
408
|
0
|
|
|
|
|
|
return undef; |
409
|
|
|
|
|
|
|
} |
410
|
0
|
|
|
|
|
|
push @tokens, [T_REDIRECT,$tmp,$fileno[0],unquote($file)]; |
411
|
|
|
|
|
|
|
} else { |
412
|
0
|
|
|
|
|
|
push @tokens, [T_REDIRECT, '>&', @fileno]; |
413
|
|
|
|
|
|
|
} |
414
|
0
|
0
|
|
|
|
|
if ($allflag) { |
415
|
0
|
|
|
|
|
|
push @tokens, [T_REDIRECT, '>&', 2, 1]; |
416
|
|
|
|
|
|
|
} |
417
|
|
|
|
|
|
|
} elsif( $tmp eq '<') { |
418
|
0
|
|
|
|
|
|
my $file; |
419
|
0
|
|
|
|
|
|
my @fileno=(0,0); |
420
|
0
|
0
|
|
|
|
|
if (@parts>0) { |
421
|
0
|
|
|
|
|
|
my $tmp= shift @parts; |
422
|
0
|
0
|
|
|
|
|
if ($tmp=~/^\[(.+?)\]$/) { |
423
|
0
|
|
|
|
|
|
@fileno= parse_fileno($1,0,0); |
424
|
0
|
0
|
|
|
|
|
if (!@fileno) { |
425
|
0
|
|
|
|
|
|
print STDERR "Illegal syntax\n"; ## FIXME |
426
|
0
|
|
|
|
|
|
return undef; |
427
|
|
|
|
|
|
|
} |
428
|
|
|
|
|
|
|
} |
429
|
|
|
|
|
|
|
else { |
430
|
0
|
|
|
|
|
|
unshift @parts, $tmp; |
431
|
|
|
|
|
|
|
} |
432
|
|
|
|
|
|
|
} |
433
|
0
|
0
|
|
|
|
|
if ($fileno[0]==0) { |
434
|
0
|
|
|
|
|
|
while( @parts>0) { |
435
|
0
|
|
|
|
|
|
$file= shift @parts; |
436
|
0
|
0
|
|
|
|
|
last if( $file !~ /^\s+$/); |
437
|
0
|
|
|
|
|
|
$file=''; |
438
|
|
|
|
|
|
|
} |
439
|
0
|
0
|
0
|
|
|
|
if( !$file or substr($file,0,1) eq '&') { |
440
|
0
|
|
|
|
|
|
Psh::Util::print_error_i18n('redirect_file_missing', |
441
|
|
|
|
|
|
|
$tmp,$Psh::bin); |
442
|
0
|
|
|
|
|
|
return undef; |
443
|
|
|
|
|
|
|
} |
444
|
0
|
|
|
|
|
|
push @tokens, [T_REDIRECT,'<',$fileno[1],unquote($file)]; |
445
|
|
|
|
|
|
|
} else { |
446
|
0
|
|
|
|
|
|
push @tokens, [T_REDIRECT,'<&',$fileno[1],$fileno[0]]; |
447
|
|
|
|
|
|
|
} |
448
|
|
|
|
|
|
|
} elsif( $tmp eq '&') { |
449
|
0
|
|
|
|
|
|
push @t, @tokens; |
450
|
0
|
|
|
|
|
|
push @t, [T_BACKGROUND],[T_END]; |
451
|
0
|
|
|
|
|
|
@tokens=(); |
452
|
|
|
|
|
|
|
} elsif( $tmp eq ';') { |
453
|
0
|
|
|
|
|
|
push @t, @tokens; |
454
|
0
|
|
|
|
|
|
push @t, [T_END]; |
455
|
0
|
|
|
|
|
|
@tokens= (); |
456
|
|
|
|
|
|
|
} elsif ($tmp eq '`') { |
457
|
0
|
|
|
|
|
|
my $tmp=''; |
458
|
0
|
|
|
|
|
|
while ( (my $tmp2= shift @parts) ne '`' ) { |
459
|
0
|
|
|
|
|
|
$tmp.=' '.$tmp2; |
460
|
|
|
|
|
|
|
} |
461
|
0
|
|
|
|
|
|
$tmp= Psh::OS::backtick($tmp); |
462
|
0
|
|
|
|
|
|
$tmp=~ s/\\/\\\\/g; |
463
|
0
|
|
|
|
|
|
$tmp=~ s/\"/\\\"/g; |
464
|
0
|
|
|
|
|
|
$tmp=~ s/\n/\\n/g; |
465
|
0
|
|
|
|
|
|
$tmp=~ s/\$/\\\$/g; |
466
|
0
|
|
|
|
|
|
$tmp=~ s/\@/\\\@/g; |
467
|
0
|
|
|
|
|
|
push @tokens, [T_WORD, join('','"', $tmp,'"')]; |
468
|
|
|
|
|
|
|
} elsif( $tmp=~ /^\s+$/) { |
469
|
|
|
|
|
|
|
} else { |
470
|
0
|
|
|
|
|
|
push @tokens, [T_WORD,$tmp]; |
471
|
|
|
|
|
|
|
} |
472
|
|
|
|
|
|
|
} |
473
|
0
|
|
|
|
|
|
push @t, @tokens; |
474
|
0
|
|
|
|
|
|
return @t; |
475
|
|
|
|
|
|
|
} |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
sub parse_line { |
478
|
0
|
|
|
0
|
0
|
|
my $line= shift; |
479
|
0
|
|
|
|
|
|
my (@use_strats) = @_; |
480
|
|
|
|
|
|
|
|
481
|
0
|
0
|
|
|
|
|
return () if substr($line,0,1) eq '#'; |
482
|
|
|
|
|
|
|
|
483
|
0
|
|
|
|
|
|
my ($lvl1,$lvl2,$lvl3); |
484
|
0
|
0
|
|
|
|
|
if (@use_strats) { |
|
|
0
|
|
|
|
|
|
485
|
0
|
|
|
|
|
|
($lvl1,$lvl2,$lvl3)= Psh::Strategy::parser_return_objects(@use_strats); |
486
|
|
|
|
|
|
|
} elsif (@Psh::temp_use_strats) { |
487
|
0
|
|
|
|
|
|
($lvl1,$lvl2,$lvl3)= Psh::Strategy::parser_return_objects(@Psh::temp_use_strats); |
488
|
|
|
|
|
|
|
} else { |
489
|
0
|
|
|
|
|
|
($lvl1,$lvl2,$lvl3)= Psh::Strategy::parser_strategy_list(); |
490
|
|
|
|
|
|
|
} |
491
|
|
|
|
|
|
|
|
492
|
0
|
0
|
|
|
|
|
if (@$lvl1) { |
493
|
0
|
|
|
|
|
|
foreach my $strategy (@$lvl1) { |
494
|
0
|
|
|
|
|
|
my $how= eval { |
495
|
0
|
|
|
|
|
|
$strategy->applies(\$line); |
496
|
|
|
|
|
|
|
}; |
497
|
0
|
0
|
|
|
|
|
if ($@) { |
|
|
0
|
|
|
|
|
|
498
|
0
|
|
|
|
|
|
print STDERR $@; |
499
|
|
|
|
|
|
|
} elsif ($how) { |
500
|
0
|
|
|
|
|
|
my $name= $strategy->name; |
501
|
0
|
|
|
|
|
|
Psh::Util::print_debug_class('s', |
502
|
|
|
|
|
|
|
"[Using strategy $name: $how]\n"); |
503
|
0
|
|
|
|
|
|
return ([ T_EXECUTE, 1, [$strategy, $how, [], [$line], $line ]]); |
504
|
|
|
|
|
|
|
} |
505
|
|
|
|
|
|
|
} |
506
|
|
|
|
|
|
|
} |
507
|
0
|
0
|
|
|
|
|
if (@$lvl2) { |
508
|
0
|
|
|
|
|
|
die "Level 2 Strategies currently not supported!"; |
509
|
|
|
|
|
|
|
} |
510
|
0
|
0
|
|
|
|
|
if (@$lvl3) { |
511
|
0
|
|
|
|
|
|
my @tokens= make_tokens( $line); |
512
|
0
|
|
|
|
|
|
my @elements=(); |
513
|
0
|
|
|
|
|
|
my $element; |
514
|
0
|
|
|
|
|
|
while( @tokens > 0) { |
515
|
0
|
|
|
|
|
|
$element=parse_complex_command(\@tokens,$lvl3); |
516
|
0
|
0
|
|
|
|
|
return undef if ! defined( $element); # TODO: Error handling |
517
|
0
|
|
|
|
|
|
push @elements, $element; |
518
|
0
|
0
|
|
|
|
|
if (@tokens > 0) { |
519
|
0
|
0
|
|
|
|
|
if ($tokens[0][0] == T_END) { |
520
|
0
|
|
|
|
|
|
shift @tokens; |
521
|
|
|
|
|
|
|
} |
522
|
0
|
0
|
|
|
|
|
if (@tokens > 0) { |
523
|
0
|
0
|
|
|
|
|
if ($tokens[0][0] == T_AND) { |
|
|
0
|
|
|
|
|
|
524
|
0
|
|
|
|
|
|
shift @tokens; |
525
|
0
|
|
|
|
|
|
push @elements, [ T_AND ]; |
526
|
|
|
|
|
|
|
} elsif ($tokens[0][0] == T_OR) { |
527
|
0
|
|
|
|
|
|
shift @tokens; |
528
|
0
|
|
|
|
|
|
push @elements, [ T_OR ]; |
529
|
|
|
|
|
|
|
} |
530
|
|
|
|
|
|
|
} |
531
|
|
|
|
|
|
|
} |
532
|
|
|
|
|
|
|
} |
533
|
0
|
|
|
|
|
|
return @elements; |
534
|
|
|
|
|
|
|
} |
535
|
|
|
|
|
|
|
} |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
sub parse_complex_command { |
538
|
0
|
|
|
0
|
0
|
|
my $tokens= shift; |
539
|
0
|
|
|
|
|
|
my $strategies= shift; |
540
|
0
|
|
|
|
|
|
my $piped= 0; |
541
|
0
|
|
|
|
|
|
my $foreground = 1; |
542
|
0
|
|
|
|
|
|
return [ T_EXECUTE, $foreground, _subparse_complex_command($tokens,$strategies,\$piped,\$foreground,{})]; |
543
|
|
|
|
|
|
|
} |
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
sub _subparse_complex_command { |
546
|
0
|
|
|
0
|
|
|
my ($tokens,$use_strats,$piped,$foreground,$alias_disabled)=@_; |
547
|
0
|
|
|
|
|
|
my @simplecommands= parse_simple_command($tokens,$use_strats, $piped,$alias_disabled,$foreground); |
548
|
|
|
|
|
|
|
|
549
|
0
|
|
0
|
|
|
|
while (@$tokens > 0 && $tokens->[0][0] == T_PIPE) { |
550
|
0
|
|
|
|
|
|
shift @$tokens; |
551
|
0
|
|
|
|
|
|
$$piped= 1; |
552
|
0
|
|
|
|
|
|
push @simplecommands, parse_simple_command($tokens,$use_strats,$piped,$alias_disabled,$foreground); |
553
|
|
|
|
|
|
|
} |
554
|
|
|
|
|
|
|
|
555
|
0
|
0
|
0
|
|
|
|
if (@$tokens > 0 && $tokens->[0][0] == T_BACKGROUND) { |
556
|
0
|
|
|
|
|
|
shift @$tokens; |
557
|
0
|
|
|
|
|
|
$$foreground = 0; |
558
|
|
|
|
|
|
|
} |
559
|
0
|
|
|
|
|
|
return @simplecommands; |
560
|
|
|
|
|
|
|
} |
561
|
|
|
|
|
|
|
|
562
|
|
|
|
|
|
|
sub parse_simple_command { |
563
|
0
|
|
|
0
|
0
|
|
my ($tokens,$use_strats,$piped,$alias_disabled,$foreground)=@_; |
564
|
0
|
|
|
|
|
|
my (@words,@options,@savetokens,@precom); |
565
|
0
|
|
|
|
|
|
my $opt={}; |
566
|
|
|
|
|
|
|
|
567
|
0
|
|
|
|
|
|
my $firstwords=1; |
568
|
0
|
|
0
|
|
|
|
while (@$tokens > 0 and |
|
|
|
0
|
|
|
|
|
569
|
|
|
|
|
|
|
($tokens->[0][0] == T_WORD or |
570
|
|
|
|
|
|
|
$tokens->[0][0] == T_REDIRECT)) { |
571
|
0
|
|
|
|
|
|
my $token = shift @$tokens; |
572
|
0
|
0
|
|
|
|
|
if ($token->[0] == T_WORD) { |
|
|
0
|
|
|
|
|
|
573
|
0
|
0
|
0
|
|
|
|
if ($firstwords and |
|
|
|
0
|
|
|
|
|
574
|
|
|
|
|
|
|
($token->[1] eq 'noglob' or |
575
|
|
|
|
|
|
|
$token->[1] eq 'noexpand' or |
576
|
|
|
|
|
|
|
$token->[1] eq 'noalias')) { |
577
|
0
|
|
|
|
|
|
push @precom, $token; |
578
|
0
|
|
|
|
|
|
$opt->{$token->[1]}=1; |
579
|
|
|
|
|
|
|
} else { |
580
|
0
|
|
|
|
|
|
$firstwords=0; |
581
|
0
|
|
|
|
|
|
push @savetokens,$token; |
582
|
0
|
|
|
|
|
|
push @words, $token->[1]; |
583
|
|
|
|
|
|
|
} |
584
|
|
|
|
|
|
|
} elsif ($token->[0] == T_REDIRECT) { |
585
|
0
|
|
|
|
|
|
push @options, $token; |
586
|
|
|
|
|
|
|
} else { |
587
|
|
|
|
|
|
|
} |
588
|
|
|
|
|
|
|
} |
589
|
|
|
|
|
|
|
|
590
|
0
|
0
|
0
|
|
|
|
if (%Psh::Support::Alias::aliases and |
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
591
|
|
|
|
|
|
|
!$opt->{noalias} and |
592
|
|
|
|
|
|
|
$Psh::Support::Alias::aliases{$words[0]} and |
593
|
|
|
|
|
|
|
!$alias_disabled->{$words[0]}) { |
594
|
0
|
|
|
|
|
|
my $alias= $Psh::Support::Alias::aliases{$words[0]}; |
595
|
0
|
|
|
|
|
|
$alias =~ s/\'/\\\'/g; |
596
|
0
|
|
|
|
|
|
$alias_disabled->{$words[0]}=1; |
597
|
0
|
|
|
|
|
|
my @tmp= make_tokens($alias); |
598
|
0
|
|
|
|
|
|
unshift @tmp, @precom; |
599
|
0
|
|
|
|
|
|
shift @savetokens; |
600
|
0
|
|
|
|
|
|
push @tmp, @savetokens; |
601
|
0
|
|
|
|
|
|
push @tmp, @options; |
602
|
0
|
|
|
|
|
|
return _subparse_complex_command(\@tmp,$use_strats,$piped,$foreground,$alias_disabled); |
603
|
|
|
|
|
|
|
} elsif (substr($words[0],0,1) eq "\\") { |
604
|
0
|
|
|
|
|
|
$words[0]=substr($words[0],1); |
605
|
|
|
|
|
|
|
} |
606
|
|
|
|
|
|
|
|
607
|
0
|
|
|
|
|
|
my $line= join ' ', @words; |
608
|
0
|
|
|
|
|
|
local $Psh::current_options= $opt; |
609
|
0
|
|
|
|
|
|
foreach my $strat (@$use_strats) { |
610
|
0
|
|
|
|
|
|
my $how= eval { |
611
|
0
|
|
|
|
|
|
$strat->applies(\$line,\@words,$$piped); |
612
|
|
|
|
|
|
|
}; |
613
|
0
|
0
|
|
|
|
|
if ($@) { |
|
|
0
|
|
|
|
|
|
614
|
0
|
|
|
|
|
|
print STDERR $@; |
615
|
|
|
|
|
|
|
} |
616
|
|
|
|
|
|
|
elsif ($how) { |
617
|
0
|
|
|
|
|
|
my $name= $strat->name; |
618
|
0
|
|
|
|
|
|
Psh::Util::print_debug_class('s', |
619
|
|
|
|
|
|
|
"[Using strategy $name: $how]\n"); |
620
|
0
|
|
|
|
|
|
return [ $strat, $how, \@options, \@words, $line, $opt]; |
621
|
|
|
|
|
|
|
} |
622
|
|
|
|
|
|
|
} |
623
|
0
|
|
|
|
|
|
Psh::Util::print_error_i18n('clueless',$line,$Psh::bin); |
624
|
0
|
|
|
|
|
|
die ''; |
625
|
|
|
|
|
|
|
} |
626
|
|
|
|
|
|
|
|
627
|
|
|
|
|
|
|
# TODO: right now this is pretty much of a hack. Could it be improved? |
628
|
|
|
|
|
|
|
# For example, 'print hello \n' on the command line gets double |
629
|
|
|
|
|
|
|
# quotes around hello and \n, so that it ends up doing |
630
|
|
|
|
|
|
|
# print("hello","\n") which looks nice but is a surprise to |
631
|
|
|
|
|
|
|
# bash users. Perhaps backslash escapes simply shouldn't be OK? |
632
|
|
|
|
|
|
|
|
633
|
|
|
|
|
|
|
sub needs_double_quotes |
634
|
|
|
|
|
|
|
{ |
635
|
0
|
|
|
0
|
0
|
|
my ($word) = @_; |
636
|
|
|
|
|
|
|
|
637
|
0
|
0
|
0
|
|
|
|
return if !defined($word) or !$word; |
638
|
|
|
|
|
|
|
|
639
|
0
|
0
|
0
|
|
|
|
if ($word =~ m/[a-zA-Z]/ # if it has some letters |
640
|
|
|
|
|
|
|
and $word =~ m!^(\\.|[$.:a-zA-Z0-9/.])*$!) { # and only these characters |
641
|
0
|
|
|
|
|
|
return 1; # then double-quote it |
642
|
|
|
|
|
|
|
} |
643
|
|
|
|
|
|
|
|
644
|
0
|
|
|
|
|
|
return 0; |
645
|
|
|
|
|
|
|
} |
646
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
|
648
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
1; |
651
|
|
|
|
|
|
|
__END__ |