/
test
executable file
·514 lines (362 loc) · 13.4 KB
/
test
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
#!/usr/bin/perl
=begin metadata
Name: test
Description: condition evaluation utility
Author: Brad Appleton
License: perl
=end metadata
=cut
###########################################################################
# test -- Perl script to emulate BSD Unix test(1) functionality
# (with slightly more informative error messages).
#
# Copyright (c) 1999 Brad Appleton. All rights reserved.
#
# This file is part of "Perl Power Tools: the Unix Reconstruction
# Project" (PPT) which is free software. You can redistribute it
# and/or modify it under the same terms as PPT itself.
#
###########################################################################
package PerlPowerTools::test;
use strict;
#use diagnostics;
use vars qw($VERSION);
$VERSION = 1.01;
## Set to 0 to disable debug output; non-zero to enable it
my $DEBUG = $ENV{DEBUG_PPT_TEST} || 0;
sub dbval($) {
local $_ = shift;
return '<undef>' unless defined;
return ($_ ? "true" : "false") if /^\d+$/;
return (/^-\w/ ? $_ : "'$_'");
}
## Keep track of errors/warnings
my $ERRORS = 0;
$SIG{__WARN__} = sub { print STDERR "test: $_[0]"; ++$ERRORS; };
$SIG{__DIE__} = sub { print STDERR "test: $_[0]"; exit 2; };
## Complain about an invalid argument
sub bad_arg(@) { warn "invalid argument @_\n"; }
## Make sure we have a numeric value
sub number($) {
local $_ = shift;
return $_ if (defined && /^[-+]?\d+$/);
bad_arg "'$_[0]' - expecting a number";
return undef;
}
## %TEST_OPS maps test(1) operators or operations to Perl operations
## If a value is a string, the string is returned. If it is a reference,
## then its assumed to be a reference to a subroutine, and the value
## is whatever the subroutine returns.
##
## The code doing the lookup is responsible for passing the correct
## number of parameters and interpreting the result.
##
my %TEST_OPS = (
## Logical/grouping operators ('-a' has higher precedence than '-o')
'(' => '(',
')' => ')',
'!' => '!',
'-a' => '&&',
'-o' => '||',
## NOTE: are && and || 'compatibility' or feature creep?
'&&' => '&&',
'||' => '||',
## File test operators
'-b' => sub { -b $_[0] },
'-c' => sub { -c $_[0] },
'-d' => sub { -d $_[0] },
'-e' => sub { -e $_[0] },
'-f' => sub { -f $_[0] },
'-g' => sub { -g $_[0] },
'-h' => sub { -l $_[0] },
'-k' => sub { -k $_[0] },
'-l' => sub { -l $_[0] },
'-p' => sub { -p $_[0] },
'-r' => sub { -r $_[0] },
'-s' => sub { -s $_[0] },
'-t' => sub { -t (number($_[0])||0) },
'-u' => sub { -u $_[0] },
'-w' => sub { -w $_[0] },
'-x' => sub { -x $_[0] },
'-B' => sub { -B $_[0] },
'-L' => sub { -l $_[0] },
'-O' => sub { -O $_[0] },
'-G' => sub { bad_arg("'-G' - operator not supported") },
'-R' => sub { -R $_[0] },
'-S' => sub { -S $_[0] },
'-T' => sub { -T $_[0] },
'-W' => sub { -W $_[0] },
'-X' => sub { -X $_[0] },
## String comparisons
'-n' => sub { length $_[0] },
'-z' => sub { ! length $_[0] },
'=' => sub { $_[0] eq $_[1] },
'!=' => sub { $_[0] ne $_[1] },
'<' => sub { $_[0] lt $_[1] },
'>' => sub { $_[0] gt $_[1] },
## NOTE: are ==, <=, and >= 'compatibility' or feature creep?
'==' => sub { $_[0] eq $_[1] },
'<=' => sub { $_[0] le $_[1] },
'>=' => sub { $_[0] ge $_[1] },
## Numeric comparisons
'-eq' => sub { (number($_[0])||0) == (number($_[1])||0) },
'-ne' => sub { (number($_[0])||0) != (number($_[1])||0) },
'-lt' => sub { (number($_[0])||0) < (number($_[1])||0) },
'-le' => sub { (number($_[0])||0) <= (number($_[1])||0) },
'-gt' => sub { (number($_[0])||0) > (number($_[1])||0) },
'-ge' => sub { (number($_[0])||0) >= (number($_[1])||0) },
## File comparisons
'-nt' => sub { -M $_[0] < -M $_[1] },
'-ot' => sub { -M $_[0] > -M $_[1] },
'-ef' => sub { bad_arg("'-ef' - operator not supported") },
);
## Apply a test operator to the given arguments
sub apply_op ($;@) {
my $test_op = shift;
my $perl_op = $TEST_OPS{$test_op};
my $result = undef;
if (defined $perl_op) {
$result = (ref $perl_op) ? 0+&{$perl_op}(@_) : $perl_op;
}
else {
## NOTE: should this be an error, or should it be the
## same as saying "-n $test_op"?
warn "invalid operator '$test_op'\n";
}
return $result;
}
## Evaluate test(1) operations and their operands.
## Returns 'undef' upon error, 0 false, !0 for true
sub test (@) {
my @terms = ();
local $_ = "";
my $grouping = 0;
my $need_expr = 1;
while (@_ > 0) {
$_ = shift;
next if m/[\[;\]]/; ## ignore '[', ']', and ';'
if (/^[\(\)!]$/) {
## grouping and negation
if ($_ eq ')') {
--$grouping;
bad_arg "'$_' - unbalanced parentheses" if ($grouping < 0);
push @terms, 0 if ($need_expr);
$need_expr = 0;
}
else {
++$grouping if ($_ eq '(');
bad_arg "'$_' - expecting '-a' or '-o'" unless ($need_expr);
}
$DEBUG and printf ">>> %s", $_;
push @terms, apply_op($_);
}
elsif (/^(?: -[ao] | \|\| | \&\& )$/x) {
## and/or operators
bad_arg "'$_' - expression expected" if ($need_expr);
$DEBUG and printf ">>> %s", $_;
push @terms, apply_op($_);
$need_expr = 1;
}
elsif ($_ eq '-t' and (@_ == 0 or !defined(number $_[0]))) {
## '-t' with no argument
bad_arg "'$_' - expecting '-a' or '-o'" unless ($need_expr);
$DEBUG and printf ">>> %s", $_;
push @terms, 0+(-t );
$need_expr = 0;
}
elsif (/^-\w$/) {
## file tests and string length/existence operators
bad_arg "'$_' - expecting '-a' or '-o'" unless ($need_expr);
warn "argument expected after '$_'\n" if (@_ == 0);
if (@_ > 0) {
$DEBUG and printf ">>> %s %s", $_, $_[0];
push @terms, apply_op($_, shift);
$need_expr = 0;
}
}
elsif (@_ and $_[0] =~ /^(?: -[a-z]{2} | [=!]?= | [<>]=? )$/x) {
## We have a string or a filename that participates
## in a binary infix operation.
bad_arg "'$_' - expecting '-a' or '-o'" unless ($need_expr);
warn "argument expected after '${\shift()}'\n" if (@_ < 2);
if (@_ >= 2) {
$DEBUG and printf ">>> %s %s %s", $_, @_[0..1];
push @terms, apply_op(shift, $_, shift);
$need_expr = 0;
}
}
else {
## We have a lone string, so "-n string" is implied.
bad_arg "'$_' - expecting '-a' or '-o'" unless ($need_expr);
$DEBUG and printf ">>> %s", dbval $_;
push @terms, length;
$need_expr = 0;
}
$DEBUG and @terms and printf " ==> %s\n", dbval($terms[-1]);
}
## Check for errors so far
warn "too many open '(' parentheses\n" if ($grouping > 0);
return undef if $ERRORS;
## Now we have all our primitive terms evaluated, compute the result.
$DEBUG and print ">>>expression is: @terms\n";
return $terms[0] if (@terms == 1 and $terms[0] =~ /^\d*$/);
my $result = eval "@terms";
warn "$@" if ($@ and !$ERRORS);
return ($@) ? undef : 0+$result;
}
exit(1) if (@ARGV == 0);
my $rc = test @ARGV;
## For command-lines, zero is success and non-zero is false, so
## we need to interpret the return code into an exit status.
exit(defined($rc) ? !$rc : 2);
__END__
=head1 NAME
test - condition evaluation utility
=head1 SYNOPSIS
test expression
[ expression ]
=head1 DESCRIPTION
The B<test> utility evaluates the I<expression> and, if it evaluates
to true, returns a zero (true) exit status; otherwise it returns 1
(false). If no expression is given, B<test> also returns 1 (false).
All operators and flags are separate arguments to the test utility.
The following primaries are used to construct expression:
=over 14
=item B<-b> I<file>
True if file exists and is a block special file.
=item B<-c> I<file>
True if file exists and is a character special file.
=item B<-d> I<file>
True if file exists and is a directory.
=item B<-e> I<file>
True if file exists (regardless of type).
=item B<-f> I<file>
True if file exists and is a regular file.
=item B<-g> I<file>
True if file exists and its set-group-ID flag is set.
=item B<-h> I<file>
True if file exists and is a symbolic link. This operator is for
COMPATABILITY purposes, do not rely on its existence. Use B<-L> instead.
=item B<-k> I<file>
True if file exists and its sticky bit is set.
=item B<-n> I<string>
True if the length of string is nonzero.
=item B<-p> I<file>
True if file is a named pipe (FIFO).
=item B<-r> I<file>
True if file exists and is readable by the effective user/group.
=item B<-s> I<file>
True if file exists and has a size greater than zero.
=item B<-t> I<file_descriptor>
True if the file whose file descriptor number is I<file_descriptor> is
open and is associated with a terminal.
=item B<-t>
Same as above with an implicit file descriptor number of "1"
(e.g.: B<-t> 1).
=item B<-u> I<file>
True if file exists and its set-user-ID flag is set.
=item B<-w> I<file>
True if file exists and is writable by the effective user/group. True
indicates only that the write flag is on. The file is not writable on
a read-only file system even if this test indicates true.
=item B<-x> I<file>
True if file exists and is executable by the effective user/group.
True indicates only that the execute flag is on. If file is a
directory, true indicates that file can be searched.
=item B<-z> I<string>
True if the length of string is zero.
=item B<-B> I<file>
True if file exists and is a binary file.
=item B<-L> I<file>
True if file exists and is a symbolic link.
=item B<-O> I<file>
True if file exists and its owner matches the effective user ID of this
process.
=begin _NOT_IMPLEMENTED_
=item B<-G> I<file>
True if file exists and its group matches the effective
group ID of this process.
=end _NOT_IMPLEMENTED_
=item B<-R> I<file>
True if file exists and is readable by the real user/group.
=item B<-S> I<file>
True if file exists and is a socket.
=item B<-T> I<file>
True if file exists and is a text file.
=item B<-W> I<file>
True if file exists and is writable by the real user/group. True
indicates only that the write flag is on. The file is not writable on
a read-only file system even if this test indicates true.
=item B<-X> I<file>
True if file exists and is executable by the real user/group.
True indicates only that the execute flag is on. If file is a
directory, true indicates that file can be searched.
=item I<file1> B<-nt> I<file2>
True if I<file1> exists and is newer than I<file2>.
=item I<file1> B<-ot> I<file2>
True if I<file1> exists and is older than I<file2>.
=begin _NOT_IMPLEMENTED_
=item I<file1> B<-ef> I<file2>
True if I<file1> and I<file2> exist and refer to the same file.
=end _NOT_IMPLEMENTED_
=item I<string>
True if I<string> is not the null string.
=item I<s1> = I<s2>
True if the strings I<s1> and I<s2> are identical.
=item I<s1> != I<s2>
True if the strings I<s1> and I<s2> are not identical.
=item I<s1> < I<s2>
True if string I<s1> comes before I<s2> based on the ASCII value
of their characters.
=item I<s1> > I<s2>
True if string I<s1> comes after I<s2> based on the ASCII value
of their characters.
=item I<s1>
True if I<s1> is not the null string.
=item I<n1> B<-eq> I<n2>
True if the integers I<n1> and I<n2> are algebraically equal.
=item I<n1> B<-ne> I<n2>
True if the integers I<n1> and I<n2> are not algebraically equal.
=item I<n1> B<-gt> I<n2>
True if the integer I<n1> is algebraically greater than the
integer I<n2>.
=item I<n1> B<-ge> I<n2>
True if the integer I<n1> is algebraically greater than or
equal to the integer I<n2>.
=item I<n1> B<-lt> I<n2>
True if the integer I<n1> is algebraically less than the integer I<n2>.
=item I<n1> B<-le> I<n2>
True if the integer I<n1> is algebraically less than or equal
to the integer I<n2>.
=back
These primaries can be combined with the following operators:
=over 14
=item ! I<expression>
True if I<expression> is false.
=item I<expression1> B<-a> I<expression2>
True if both I<expression1> and I<expression2> are true.
=item I<expression1> B<-o> I<expression2>
True if either I<expression1> or I<expression2> are true.
=item (I<expression>)
True if I<expression> is true (uses parentheses for grouping).
=back
Note that the B<-a> operator has higher precedence than the B<-o> operator.
Notice also that all the operators and flags are separate arguments to test.
=head1 RETURN VALUES
The test utility exits with one of the following values:
=over 8
=item $return_val == 0
Expression evaluated to true.
=item $return_val == 1
Expression evaluated to false or expression was missing.
=item $return_val > 1
An error occurred.
=back
=head1 CAVEATS
Command line arguments like parentheses and arithmetic operators
(e.g.: '(', ')', '!', '>', '<', etc.) I<may> be meaningful to the
command-line interpreter (shell) and therefore I<may> need to be
escaped from any special shell interpretation.
=head1 SEE ALSO
L<sh>, L<find>
=cut