-
Notifications
You must be signed in to change notification settings - Fork 13
/
rm-src-dups
executable file
·127 lines (105 loc) · 2.53 KB
/
rm-src-dups
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
#!/usr/bin/perl
use strict;
use warnings;
use File::Find;
use File::Spec;
use FindBin qw($RealBin);
use Getopt::Long;
use List::Util;
sub usage {
die <<EOUSAGE;
Usage: $RealBin [options] SRC DST
Removes files from SRC which were already identically in DST.
Options:
-n, --dry-run Just show what would have been removed
-k, --keep-dirs Don't prune empty directories
-s SUBPATH Only work in SUBPATH of SRC and DST
-v, --verbose
-h, --help
EOUSAGE
}
my %opts = ();
Getopt::Long::Configure('bundling');
GetOptions(
\%opts,
'dry-run|n', 'keep-dirs|k', 'subpath|s=s', 'verbose|v', 'help|h'
) or usage();
usage() if $opts{help} || @ARGV != 2;
my ($src, $dst) = @ARGV;
if ($opts{subpath}) {
$src = File::Spec->join($src, $opts{subpath});
$dst = File::Spec->join($dst, $opts{subpath});
}
my %doomed = ();
rm_src_dups($src, $dst);
unless ($opts{'keep-dirs'}) {
#chdir "/" or die "chdir(/) failed: $!\n";
finddepth({ wanted => \&prune_empty_dirs, no_chdir => 1}, $src);
}
exit 0;
sub rm_src_dups {
my ($src, $dst) = @_;
-d $src or die "$src isn't a directory\n";
-d $dst or die "$dst isn't a directory\n";
my $pid = open(DIFF, "-|");
defined $pid or die "fork failed: $!\n";
if (! $pid) {
# in child
my @args = ( "diff", '-r', '--report-identical-files', '--brief', $src, $dst );
exec @args or die "exec(@args) failed: $!\n";
}
while (<DIFF>) {
chomp;
if (/^Files (.+) and (\Q$dst\E.+) are identical$/) {
do_unlink($1);
next;
}
elsif (/^Files (.+) and (\Q$dst\E.+) differ$/) {
warn "$1 and $2 differ\n" if $opts{verbose};
next;
}
}
}
sub do_unlink {
my ($doomed) = @_;
if ($opts{'dry-run'}) {
warn qq{rm "$doomed"\n};
$doomed{$doomed}++;
}
else {
if (unlink($doomed)) {
warn qq{rm "$doomed"\n} if $opts{verbose};
}
else {
warn "unlink($doomed) failed: $!\n";
}
}
}
sub prune_empty_dirs {
return unless -d;
if (dir_is_empty($_)) {
if ($opts{'dry-run'}) {
warn qq{rmdir "$_"\n};
$doomed{$_}++;
}
else {
warn qq{rmdir "$_"\n} if $opts{verbose};
rmdir($_) or die "rmdir($_) failed: $!\n";
}
}
else {
warn qq{Leaving non-empty dir "$_"\n} if $opts{verbose};
}
}
sub dir_is_empty {
my ($dir) = @_;
opendir(DIR, $dir) or die "opendir($dir) failed: $!\n";
my $got_one = 0;
while (my $dirent = readdir(DIR)) {
next if $dirent eq '.' or $dirent eq '..' or $doomed{"$dir/$dirent"};
$got_one++;
last;
}
closedir(DIR);
return $got_one ? 0 : 1;
}