line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#!/usr/bin/perl |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
=encoding utf8 |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
=begin metadata |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
Name: rm |
8
|
|
|
|
|
|
|
Description: remove directory entries |
9
|
|
|
|
|
|
|
Author: brian d foy, bdfoy@cpan.org |
10
|
|
|
|
|
|
|
License: artistic2 |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
=end metadata |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
=cut |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
=head1 NAME |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
rm - remove directory entries |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
=head1 SYNOPSIS |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
rm [-fiPrR] file ... |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
=head1 DESCRIPTION |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
=head1 OPTIONS |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
=over 4 |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
=item * -f - do not prompt the user for each file, and do not consider it an error if a file cannot be removed |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
=item * -i - prompt the user for each file. |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
=item * -P - a no-op, for compatibility. So implementations would overwrite files with random data |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
=item * -r - same as -R |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
=item * -R - remove directories recursively |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
=item * -v |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
=back |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
=head1 AUTHOR |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
Copyright (c) brian d foy, bdfoy@cpan.org |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
The original version of this program was written by Steve Kemp, |
49
|
|
|
|
|
|
|
steve@steve.org.uk, but almost none of that remains. |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
=head1 LICENCE |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
This program is licensed under the Artistic License 2.0. |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
=cut |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
package PerlPowerTools::rm; |
58
|
|
|
|
|
|
|
|
59
|
2
|
|
|
2
|
|
998
|
use strict; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
64
|
|
60
|
|
|
|
|
|
|
|
61
|
2
|
|
|
2
|
|
12
|
use File::Basename; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
129
|
|
62
|
2
|
|
|
2
|
|
930
|
use File::Spec::Functions; |
|
2
|
|
|
|
|
1631
|
|
|
2
|
|
|
|
|
144
|
|
63
|
2
|
|
|
2
|
|
1282
|
use Storable qw(dclone); |
|
2
|
|
|
|
|
6620
|
|
|
2
|
|
|
|
|
139
|
|
64
|
|
|
|
|
|
|
|
65
|
2
|
|
|
2
|
|
18
|
use constant EX_SUCCESS => 0; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
172
|
|
66
|
2
|
|
|
2
|
|
11
|
use constant EX_FAILURE => 1; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
109
|
|
67
|
2
|
|
|
2
|
|
11
|
use constant EX_USAGE => 2; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
84
|
|
68
|
2
|
|
|
2
|
|
10
|
use constant OP_SUCCEEDED => 0; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
87
|
|
69
|
2
|
|
|
2
|
|
10
|
use constant OP_FAILED => 1; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
3562
|
|
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
my $Program = basename($0); |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
__PACKAGE__->run( args => \@ARGV ) unless caller; |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
sub run { |
76
|
10
|
|
|
10
|
|
88013
|
my $class = shift; |
77
|
10
|
|
|
|
|
29
|
my %args = @_; |
78
|
|
|
|
|
|
|
|
79
|
10
|
|
|
|
|
18
|
my $args = delete $args{args}; |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
# This looks funny because the other args are filehandles, which |
82
|
|
|
|
|
|
|
# we can't dupe. We want to play with the command-line args such |
83
|
|
|
|
|
|
|
# that we don't mess up anything that called us. |
84
|
10
|
|
|
|
|
543
|
my $self = $class->new( { args => dclone($args), %args } )->process_options; |
85
|
|
|
|
|
|
|
|
86
|
10
|
100
|
|
|
|
33
|
$self->error( "$Program: -P ignored\n" ) if $self->is_overwrite; |
87
|
|
|
|
|
|
|
|
88
|
10
|
50
|
|
|
|
17
|
unless ( () = $self->files ) { |
89
|
0
|
|
|
|
|
0
|
$self->error( "$Program: missing argument\n" ); |
90
|
0
|
|
|
|
|
0
|
exit EX_FAILURE; |
91
|
|
|
|
|
|
|
} |
92
|
|
|
|
|
|
|
|
93
|
10
|
|
|
|
|
14
|
my $errors = grep { $self->process_file( $_ ) } $self->files; |
|
13
|
|
|
|
|
21
|
|
94
|
10
|
100
|
|
|
|
43
|
$self->exit( $errors ? EX_FAILURE : EX_SUCCESS ); |
95
|
|
|
|
|
|
|
} |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
sub new { |
98
|
32
|
|
|
32
|
|
33898
|
my( $class, $args ) = @_; |
99
|
32
|
|
|
|
|
70
|
bless { |
100
|
|
|
|
|
|
|
$class->defaults, |
101
|
|
|
|
|
|
|
%$args |
102
|
|
|
|
|
|
|
}, $class; |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
sub defaults { |
106
|
32
|
|
|
32
|
|
303
|
my %hash = ( |
107
|
|
|
|
|
|
|
args => [], |
108
|
|
|
|
|
|
|
error_fh => \*STDERR, |
109
|
|
|
|
|
|
|
output_fh => \*STDOUT, |
110
|
|
|
|
|
|
|
); |
111
|
|
|
|
|
|
|
} |
112
|
|
|
|
|
|
|
|
113
|
0
|
|
|
0
|
|
0
|
sub exit { my $self = shift; exit(shift) } |
|
0
|
|
|
|
|
0
|
|
114
|
|
|
|
|
|
|
|
115
|
30
|
|
|
30
|
|
6284
|
sub files { my $self = shift; @{ $self->{files} } } |
|
30
|
|
|
|
|
34
|
|
|
30
|
|
|
|
|
83
|
|
116
|
|
|
|
|
|
|
|
117
|
44
|
|
|
44
|
|
13774
|
sub is_force { my $self = shift; $self->{options}{f} } |
|
44
|
|
|
|
|
163
|
|
118
|
25
|
|
|
25
|
|
42
|
sub is_interactive { my $self = shift; $self->{options}{i} } |
|
25
|
|
|
|
|
79
|
|
119
|
20
|
|
|
20
|
|
34
|
sub is_overwrite { my $self = shift; $self->{options}{P} } |
|
20
|
|
|
|
|
60
|
|
120
|
14
|
|
|
14
|
|
21
|
sub is_recursive { my $self = shift; $self->{options}{R} } |
|
14
|
|
|
|
|
52
|
|
121
|
19
|
|
|
19
|
|
33
|
sub is_verbose { my $self = shift; $self->{options}{v} } |
|
19
|
|
|
|
|
88
|
|
122
|
|
|
|
|
|
|
|
123
|
10
|
|
|
10
|
|
4244
|
sub options { my $self = shift; $self->{options} } |
|
10
|
|
|
|
|
23
|
|
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
sub preprocess_options { |
126
|
32
|
|
|
32
|
|
50
|
my( $self ) = @_; |
127
|
|
|
|
|
|
|
|
128
|
32
|
|
|
|
|
34
|
my @new_args = @{ $self->{args} }; |
|
32
|
|
|
|
|
135
|
|
129
|
|
|
|
|
|
|
|
130
|
32
|
|
|
|
|
84
|
my %args = map { $new_args[$_], $_ } 0 .. $#new_args; |
|
86
|
|
|
|
|
212
|
|
131
|
|
|
|
|
|
|
|
132
|
32
|
|
|
|
|
49
|
my @rest; |
133
|
32
|
100
|
|
|
|
76
|
if( exists $args{'--'} ) { |
134
|
9
|
|
|
|
|
26
|
@rest = @new_args[ $args{'--'} .. $#new_args ]; |
135
|
9
|
|
|
|
|
20
|
@new_args = @new_args[0 .. ($args{'--'} - 1)]; |
136
|
|
|
|
|
|
|
} |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
# Expand clustering |
139
|
|
|
|
|
|
|
@new_args = map { |
140
|
32
|
100
|
|
|
|
39
|
if( /-(.+)/ ) { |
|
64
|
|
|
|
|
194
|
|
141
|
35
|
|
|
|
|
66
|
my $cluster = $1; |
142
|
35
|
|
|
|
|
71
|
map { "-$_" } split //, $cluster; |
|
47
|
|
|
|
|
122
|
|
143
|
|
|
|
|
|
|
} |
144
|
|
|
|
|
|
|
else { |
145
|
29
|
|
|
|
|
53
|
$_; |
146
|
|
|
|
|
|
|
} |
147
|
|
|
|
|
|
|
} @new_args; |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
# this is rm particular processing: -f and -i turn off each |
150
|
|
|
|
|
|
|
# other, and the last one wins. Figure out which one is last |
151
|
|
|
|
|
|
|
# then filter out all earlier of the other. |
152
|
32
|
100
|
100
|
|
|
100
|
if( exists $args{'-f'} && exists $args{'-i'} ) { |
153
|
6
|
|
|
|
|
7
|
my $last; |
154
|
6
|
|
|
|
|
9
|
foreach ( reverse @new_args ) { |
155
|
6
|
50
|
|
|
|
22
|
next unless /\A-[fi]\z/; |
156
|
6
|
|
|
|
|
8
|
$last = $_; |
157
|
6
|
|
|
|
|
8
|
last; |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
@new_args = map { |
161
|
6
|
|
|
|
|
10
|
( |
162
|
12
|
100
|
100
|
|
|
65
|
( $last eq '-f' and $_ eq '-i') # f wins |
163
|
|
|
|
|
|
|
|| |
164
|
|
|
|
|
|
|
( $last ne '-f' and $_ eq '-f' ) # i wins |
165
|
|
|
|
|
|
|
) ? () : $_; |
166
|
|
|
|
|
|
|
} @new_args; |
167
|
|
|
|
|
|
|
} |
168
|
|
|
|
|
|
|
|
169
|
32
|
|
|
|
|
49
|
$self->{original_args} = $self->{args}; |
170
|
32
|
|
|
|
|
67
|
$self->{args} = $self->{preprocessed_args} = [ @new_args, @rest ]; |
171
|
|
|
|
|
|
|
|
172
|
32
|
|
|
|
|
77
|
return $self; |
173
|
|
|
|
|
|
|
} |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
sub process_options { |
176
|
20
|
|
|
20
|
|
66
|
my( $self ) = @_; |
177
|
|
|
|
|
|
|
|
178
|
20
|
|
|
|
|
59
|
$self->preprocess_options; |
179
|
|
|
|
|
|
|
|
180
|
20
|
|
|
|
|
1648
|
require Getopt::Long; |
181
|
|
|
|
|
|
|
|
182
|
20
|
|
|
|
|
21500
|
my %opts; |
183
|
|
|
|
|
|
|
my $ret = Getopt::Long::GetOptionsFromArray( |
184
|
|
|
|
|
|
|
$self->{args}, |
185
|
|
|
|
|
|
|
'f' => \$opts{'f'}, |
186
|
|
|
|
|
|
|
'i' => \$opts{'i'}, |
187
|
|
|
|
|
|
|
'P' => \$opts{'P'}, |
188
|
|
|
|
|
|
|
'R' => \$opts{'r'}, |
189
|
|
|
|
|
|
|
'r' => \$opts{'R'}, |
190
|
20
|
|
|
|
|
93
|
'v' => \$opts{'v'}, |
191
|
|
|
|
|
|
|
); |
192
|
|
|
|
|
|
|
|
193
|
20
|
100
|
|
|
|
9620
|
$self->{options} = { map { defined $_ ? $_ : 0 } %opts }; |
|
240
|
|
|
|
|
360
|
|
194
|
20
|
|
|
|
|
50
|
$self->{files} = $self->{args}; |
195
|
|
|
|
|
|
|
|
196
|
20
|
|
|
|
|
47
|
return $self; |
197
|
|
|
|
|
|
|
} |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
sub process_file { |
200
|
13
|
|
|
13
|
|
23
|
my( $self, $filename ) = @_; |
201
|
|
|
|
|
|
|
|
202
|
13
|
|
|
|
|
12
|
my $method = do { |
203
|
13
|
100
|
|
|
|
188
|
if( -d $filename ) { |
204
|
4
|
100
|
|
|
|
16
|
if( ! $self->is_recursive ) { |
205
|
2
|
100
|
|
|
|
5
|
$self->error( "$Program: '$filename': is a directory\n" ) unless $self->is_force; |
206
|
2
|
100
|
|
|
|
7
|
return $self->is_force ? OP_SUCCEEDED : OP_FAILED; |
207
|
|
|
|
|
|
|
} |
208
|
2
|
|
|
|
|
5
|
'remove_directory'; |
209
|
|
|
|
|
|
|
} |
210
|
|
|
|
|
|
|
else { |
211
|
9
|
|
|
|
|
23
|
'remove_file'; |
212
|
|
|
|
|
|
|
} |
213
|
|
|
|
|
|
|
}; |
214
|
|
|
|
|
|
|
|
215
|
11
|
|
|
|
|
38
|
my $result = $self->$method( $filename ); |
216
|
11
|
100
|
|
|
|
18
|
return $self->is_force ? OP_SUCCEEDED : $result; |
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
sub remove_directory { |
220
|
2
|
|
|
2
|
|
5
|
my( $self, $dirname ) = @_; |
221
|
|
|
|
|
|
|
|
222
|
2
|
|
|
|
|
3
|
my $dh; |
223
|
2
|
50
|
|
|
|
56
|
unless( opendir( $dh, $dirname ) ) { |
224
|
0
|
0
|
|
|
|
0
|
$self->error( "$Program: cannot open '$dirname': $!\n" ) unless $self->is_force; |
225
|
0
|
0
|
|
|
|
0
|
return $self->is_force ? OP_SUCCEEDED : OP_FAILED; |
226
|
|
|
|
|
|
|
} |
227
|
|
|
|
|
|
|
|
228
|
2
|
|
|
|
|
47
|
foreach my $file ( readdir($dh) ) { |
229
|
6
|
100
|
100
|
|
|
27
|
next if $file eq '.' || $file eq '..'; |
230
|
2
|
|
|
|
|
13
|
my $path = catfile( $dirname, $file ); |
231
|
|
|
|
|
|
|
|
232
|
2
|
50
|
|
|
|
25
|
my $method = -d $path ? 'remove_directory' : 'remove_file'; |
233
|
2
|
|
|
|
|
8
|
my $result = $self->$method($path); |
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
|
236
|
2
|
|
|
|
|
27
|
closedir $dh; |
237
|
|
|
|
|
|
|
|
238
|
2
|
50
|
|
|
|
90
|
unless( rmdir $dirname ) { |
239
|
0
|
0
|
|
|
|
0
|
$self->error( "$Program: cannot remove directory '$dirname': $!\n" ) unless $self->is_force; |
240
|
0
|
0
|
|
|
|
0
|
return $self->is_force ? OP_SUCCEEDED : OP_FAILED; |
241
|
|
|
|
|
|
|
} |
242
|
|
|
|
|
|
|
|
243
|
2
|
50
|
|
|
|
8
|
$self->message( "$dirname\n" ) if $self->is_verbose; |
244
|
|
|
|
|
|
|
|
245
|
2
|
|
|
|
|
9
|
return OP_SUCCEEDED; |
246
|
|
|
|
|
|
|
} |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
sub remove_file { |
249
|
11
|
|
|
11
|
|
18
|
my( $self, $filename ) = @_; |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
# Answering no to skip a file is not an error |
252
|
11
|
50
|
66
|
|
|
120
|
if( ! -w $filename && $self->is_interactive ) { |
|
|
50
|
|
|
|
|
|
253
|
0
|
|
|
|
|
0
|
$self->message( "$filename: Read-only ? " ); |
254
|
0
|
0
|
|
|
|
0
|
return OP_SUCCEEDED if =~ /^[Nn]/; |
255
|
|
|
|
|
|
|
} |
256
|
|
|
|
|
|
|
elsif( $self->is_interactive ) { |
257
|
0
|
|
|
|
|
0
|
$self->message( "$filename: ? " ); |
258
|
0
|
0
|
|
|
|
0
|
return OP_SUCCEEDED if =~ /^[Nn]/; |
259
|
|
|
|
|
|
|
} |
260
|
|
|
|
|
|
|
|
261
|
11
|
100
|
|
|
|
19
|
chmod '0777', $filename if $self->is_force; |
262
|
|
|
|
|
|
|
|
263
|
11
|
100
|
|
|
|
358
|
unless( unlink $filename ) { |
264
|
4
|
100
|
|
|
|
13
|
$self->error( "$Program: cannot remove '$filename': $!\n" ) unless $self->is_force; |
265
|
4
|
100
|
|
|
|
6
|
return $self->is_force ? OP_SUCCEEDED : OP_FAILED; |
266
|
|
|
|
|
|
|
} |
267
|
|
|
|
|
|
|
|
268
|
7
|
50
|
|
|
|
30
|
$self->message( "$filename\n" ) if $self->is_verbose; |
269
|
|
|
|
|
|
|
|
270
|
7
|
|
|
|
|
16
|
return OP_SUCCEEDED; |
271
|
|
|
|
|
|
|
} |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
sub usage { |
274
|
0
|
|
|
0
|
|
0
|
require Pod::Usage; |
275
|
0
|
|
|
|
|
0
|
Pod::Usage::pod2usage({ |
276
|
|
|
|
|
|
|
-exitval => EX_USAGE, |
277
|
|
|
|
|
|
|
-verbose => 2, |
278
|
|
|
|
|
|
|
}); |
279
|
|
|
|
|
|
|
} |
280
|
|
|
|
|
|
|
|
281
|
4
|
|
|
4
|
|
4
|
sub error_fh { my $self = shift; $self->{error_fh} } |
|
4
|
|
|
|
|
27
|
|
282
|
|
|
|
|
|
|
sub error { |
283
|
4
|
|
|
4
|
|
9
|
my $self = shift; |
284
|
4
|
50
|
|
|
|
5
|
print { $self->error_fh || * STDERR } @_; |
|
4
|
|
|
|
|
5
|
|
285
|
|
|
|
|
|
|
} |
286
|
|
|
|
|
|
|
|
287
|
0
|
|
|
0
|
|
|
sub output_fh { my $self = shift; $self->{output_fh} } |
|
0
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
sub message { |
289
|
0
|
|
|
0
|
|
|
my $self = shift; |
290
|
0
|
0
|
|
|
|
|
print { $self->output_fh || *STDOUT } @_; |
|
0
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
} |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
__PACKAGE__; |