publicscripts/mvwrap/mvwrap

289 lines
6.7 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
&paranoia(\@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;
}