289 lines
6.6 KiB
Perl
Executable file
289 lines
6.6 KiB
Perl
Executable file
#!/usr/bin/perl -w
|
|
|
|
# $Id$
|
|
|
|
#
|
|
# Script to let an editor loose on a directory listing
|
|
# Much easier than doing 6000 moves by hand
|
|
# I think it's relatively safe now
|
|
# (C) 2001 Ward Wouts
|
|
#
|
|
|
|
use IO::File;
|
|
use File::Copy;
|
|
use POSIX qw(tmpnam);
|
|
use Getopt::Long;
|
|
|
|
&cmdline;
|
|
unless (defined @source) { &read_cur_dir; }
|
|
&lock;
|
|
|
|
if (@pattern) {
|
|
@target = @source;
|
|
&pattern_edit;
|
|
} else {
|
|
$temp_file = &open_temp;
|
|
&edit($temp_file);
|
|
&read_temp($temp_file);
|
|
}
|
|
|
|
&run_checks;
|
|
|
|
if ($paranoia) { # extra checks for a specified list of files
|
|
¶noia(\@source, \@target);
|
|
}
|
|
|
|
# if it's unsafe to move files directly, move the unsafe ones to a
|
|
# temporary file first
|
|
if (@unsafe = &check_safety(\@source, \@target)) {
|
|
&safety_belt(\@unsafe, \@source);
|
|
}
|
|
|
|
|
|
&move_files(\@source, \@target);
|
|
if ( -e ".mv_wrap" ) { unlink (".mv_wrap"); }
|
|
|
|
|
|
########################################################
|
|
# sub routines from here on...
|
|
#
|
|
|
|
#Read current dir
|
|
sub read_cur_dir {
|
|
my (@dir, $filename);
|
|
opendir(DIRHANDLE, ".") or die "couldn't open .: $!";
|
|
while ( defined ($filename = readdir(DIRHANDLE)) ) {
|
|
unless ($filename eq "." | $filename eq "..") {
|
|
push @dir, "$filename\n";
|
|
}
|
|
}
|
|
closedir(DIRHANDLE);
|
|
@source = sort @dir;
|
|
}
|
|
|
|
# call EDITOR or vi with filename
|
|
# edit($filename);
|
|
sub edit {
|
|
my $filename = shift;
|
|
@editor = (defined $ENV{EDITOR} ? $ENV{EDITOR} : "vi", "$filename");
|
|
if ($opt_e) { @editor = ($opt_e, "$filename") }
|
|
system(@editor) == 0
|
|
or die "System @editor failed: $?";
|
|
}
|
|
|
|
# make tempfiles and install handler to remove them
|
|
sub open_temp {
|
|
my $target;
|
|
do { $target_name = tmpnam() }
|
|
until $target = IO::File->new($target_name, O_RDWR|O_CREAT|O_EXCL);
|
|
END { if ($opt_e) { unlink($target_name) or die "Couldn't unlink $target_name: $!";} }
|
|
foreach (@source) {
|
|
print $target $_;
|
|
}
|
|
close ($target);
|
|
return $target_name;
|
|
}
|
|
|
|
sub read_temp {
|
|
my $target_name = shift;
|
|
open TARGET, $target_name or die "Couldn't open tempfile: $target_name: $!";
|
|
@target = <TARGET>;
|
|
close TARGET;
|
|
}
|
|
|
|
# Moves files from the names in one array to the names in another array
|
|
# Must be called like:
|
|
# move_files(\@source, \@target)
|
|
sub move_files {
|
|
my ($from, $to) = @_;
|
|
my ($i, $source, $target);
|
|
for ( $i=0; $i < scalar(@$from); $i++ ) {
|
|
$source=$from->[$i];
|
|
$target=$to->[$i];
|
|
chomp($source);
|
|
chomp($target);
|
|
unless ( $source eq $target ) {
|
|
if ($opt_v||$opt_n) {
|
|
print "mv $source $target\n";
|
|
}
|
|
unless ($opt_n) {
|
|
move("$source", "$target")
|
|
or die "move failed: $!";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# read pattern file
|
|
sub read_pattern {
|
|
open PAT, "< $opt_f" or die "Couldn't open pattern file: $?\n";
|
|
@pattern = <PAT>;
|
|
close PAT;
|
|
}
|
|
|
|
# use patterns to change filenames
|
|
sub pattern_edit {
|
|
my (@new_target, $pat);
|
|
foreach $pat (@pattern) {
|
|
@new_target=();
|
|
foreach(@target) {
|
|
eval $pat;
|
|
push @new_target, $_;
|
|
}
|
|
@target = @new_target;
|
|
}
|
|
}
|
|
|
|
sub run_checks {
|
|
my $line;
|
|
unless ( scalar(@source) == scalar(@target) ) {
|
|
if ( -e ".mv_wrap" ) { unlink (".mv_wrap"); }
|
|
die "Aborting. Source and target list don't have the same number of lines.\n";
|
|
}
|
|
|
|
foreach $line (@target) {
|
|
if ( $line =~ m/^$/ ) {
|
|
if ( -e ".mv_wrap" ) { unlink (".mv_wrap"); }
|
|
die "Aborting. You can't move to empty names.\n";
|
|
}
|
|
}
|
|
|
|
if ( &check_unique(@target) ) {
|
|
if ( -e ".mv_wrap" ) { unlink (".mv_wrap"); }
|
|
die "Aborting. You're trying to move multiple files to the same name.\n";
|
|
}
|
|
}
|
|
|
|
# returns 0 if all entries in @target_list are unique. 1 if not.
|
|
sub check_unique (@target_list){
|
|
my @uniqu;
|
|
my @target_list = @_;
|
|
my %seen = ();
|
|
@uniqu = grep { ! $seen{$_} ++ } @target_list;
|
|
return (scalar(@uniqu) != scalar(@target))
|
|
}
|
|
|
|
# Compares one array of file names to another, making sure files
|
|
# can be moved safely without overwriting each other
|
|
# Must be called like:
|
|
# check_safety(\@source, \@target)
|
|
# Returns an array of unsafe line numbers
|
|
sub check_safety {
|
|
my ($from, $to) = @_;
|
|
my ($i, $j, @danger, @unique, %seen);
|
|
my ($a, $b);
|
|
for ( $i=0 ; $i < scalar(@$from) ; $i++ ) {
|
|
for ( $j=0; $j < scalar(@$to); $j++ ) {
|
|
$a = $from->[$i]; chomp($a);
|
|
$b = $to->[$j]; chomp($b);
|
|
if (($a eq $b) && ($i != $j)) {
|
|
push @danger, $i;
|
|
push @danger, $j;
|
|
}
|
|
}
|
|
}
|
|
%seen = ();
|
|
@unique = grep { ! $seen{$_} ++ } @danger;
|
|
return @unique;
|
|
}
|
|
|
|
# generate random filename, which does not exist
|
|
sub rand_file {
|
|
my $filename;
|
|
my @chars=( "A" .. "Z", "a" .. "z", 0 .. 9);
|
|
while ( -e ($filename = join("", @chars[ map { rand @chars } ( 1 .. 8 ) ]))) {}
|
|
return $filename;
|
|
}
|
|
|
|
# Moves files to a random filename and updates the source array
|
|
# Must be called like:
|
|
# safety_belt(\@unsafe, \@source)
|
|
sub safety_belt {
|
|
my ($unsafe, $source) = @_;
|
|
my($filenr, $filename, $rand);
|
|
foreach $filenr (@$unsafe) {
|
|
$filename = $source->[$filenr];
|
|
chomp $filename;
|
|
$rand = &rand_file;
|
|
if ($opt_v||$opt_n) {
|
|
print "mv $filename $rand\n";
|
|
}
|
|
unless ($opt_n) {
|
|
move("$filename", "$rand")
|
|
or die "move failed: $!";
|
|
}
|
|
$source->[$filenr] = "$rand\n";
|
|
}
|
|
}
|
|
|
|
# Extra safety checks when one's useing a specified list of files
|
|
# Must be called like:
|
|
# paranoia(\@unsafe, \@source)
|
|
sub paranoia {
|
|
my ($source, $target) =@_;
|
|
my $safe;
|
|
foreach $ctarget (@$target) {
|
|
$safe = 0;
|
|
foreach $csource (@$source) {
|
|
if ($ctarget eq $csource) { $safe = 1; }
|
|
}
|
|
chomp $ctarget;
|
|
if (! $safe && -e $ctarget) { die "That would overwrite files\n"; }
|
|
}
|
|
}
|
|
|
|
sub lock {
|
|
if ( -e ".mv_wrap") {
|
|
die "Another mv_wrap process is active in this direcory\n"
|
|
}
|
|
else {
|
|
open LOCK, "> .mv_wrap" or die "Couldn't open .mv_wrap for writing: $?\n";
|
|
print LOCK $$;
|
|
close LOCK;
|
|
}
|
|
}
|
|
|
|
sub unlock {
|
|
unlink(".mv_wrap") or die "Couldn't remove lock file: $!\n";
|
|
}
|
|
|
|
sub cmdline {
|
|
%optctl = ();
|
|
&GetOptions("e=s", "h", "p=s", \@pattern, "f=s", "v", "n");
|
|
&GetOptions(\%optctl, "e");
|
|
|
|
&help if $opt_h;
|
|
if ($opt_f) { &read_pattern; }
|
|
if (defined @ARGV) {
|
|
foreach (@ARGV) {
|
|
push @source, "$_\n";
|
|
}
|
|
$paranoia = 1;
|
|
}
|
|
}
|
|
|
|
sub help {
|
|
$opt_h = 1; # just get rid of that stupid message from -w
|
|
print << "EOT";
|
|
|
|
This is a little script to make renaming large quantities of files easy.
|
|
It basically lets you edit the names in a directory with an editor
|
|
of your choice. By default this will be "vi", but it honors the EDITOR
|
|
environment variable. Which in turn can be overridden with the -e option.
|
|
|
|
$0 [options] [--] [files]
|
|
|
|
At the moment it takes the following options:
|
|
-h this help message
|
|
-e <editor> invoke with this editor; ignored if -f or -p is given
|
|
-p <pattern> use a pattern to edit; ignored if -f is given
|
|
-f <file> use a pattern file to edit
|
|
-v verbose; shows file moves
|
|
-n test; doesn't move, implies -v
|
|
|
|
Specifying files on the command line is still unsafe! There are no checks
|
|
to prevent overwriting non-specified files!
|
|
|
|
EOT
|
|
exit;
|
|
}
|