Re: ITP: renameutils -- A set of programs to make renaming of multiple files easier.
On Sun, Jul 28, 2002 at 09:52:30PM +0200, Amaya wrote:
> Package: wnpp
> Version: N/A; reported 2002-07-28
> Severity: wishlist
>
> If nobody objects, expect an upload tonight.
I wrote a similar script quite a while ago (mvm), which does the same
thing you're describing. It's not solid (eg. renaming directories in
recursive mode is probably a bad idea, though it's smart enough to
handle filename swaps), but I've found it useful.
I'll attach it, for the sake of comparison; maybe you'll find something
worth adding to your script.
> The file renaming utilities (renameutils for short) are a set of programs
> designed to make renaming of multiple files faster and less cumbersome.
>
> The file renaming utilities consists of two programs, qmv and imv. The first
> one, qmv, allows files to be renamed by editing their names in any text
> editor. By changing a letter in a text document, a letter in a filename can be
> changed. Since the files are listed after each other, common changes can be
> made more quickly.
Or quickly do things like :%s/_/ /g, without playing with the shell, and
swap X and Y without having to do X->A/Y->X/A->Y by hand.
--
Glenn Maynard
#!/usr/bin/perl
$ENV{'EDITOR'} || die "Environment variable EDITOR not set\n";
use Getopt::Long;
Getopt::Long::config("bundling");
GetOptions("v|verbose" => \$verbose, "debug" => \$debug,
"r|recurse" => \$recurse,
"f|files" => \$filesonly,
"dotfiles" => \$dotfiles) || exit 1;
if($debug) {
# debug implies verbose
$verbose = 1;
}
if ($#ARGV == -1) {
push @dirs, '.';
} else {
@dirs = @ARGV;
}
if(hasdupes(@dirs))
{
warn "Duplicate sources; nothing moved.\n";
oops(@newf);
exit 1;
}
# We can only add directories only, or non-directories only,
# to the rename list (either recursively or not); not both,
# as we want to avoid allowing the user to change a filename
# in a directory *and* the directory name itself. We could
# probably handle this: rename files first, then directories,
# but supporting these types of adds is useful anyway.
while($dir = shift(@dirs)) {
adddir ($dir);
}
# Write the file to be edited.
@files = sort(@files);
open TMPF, ">.mrename.tmp.$$";
foreach ( @files )
{
print TMPF "$_\n";
}
close(TMPF);
$#files == -1 && die "No files to rename.";
# Edit it.
system("$ENV{'EDITOR'} .mrename.tmp.$$");
# Reread it.
open TMPF, "<.mrename.tmp.$$" || die "Couldn't reopen\n";
while(<TMPF>) { chomp; push @newf, $_; }
close(TMPF);
unlink ".mrename.tmp.$$";
if($#newf != $#files) {
warn "Number of files changed; nothing moved.\n";
oops(@newf);
exit 1;
}
if(hasdupes(@newf))
{
warn "Duplicate destinations; nothing moved.\n";
oops(@newf);
exit 1;
}
for ($n = 0; $n <= $#newf; $n++) {
$dir1 = $files[$n];
$dir2 = $newf[$n];
# if the last character in the original is a slash, it's a
# directory name, so drop it.
if($files[$n] =~ /\/$/) {
# This is a directory; discard any trailing slash:
$dir1 =~ s/\/?$//;
$dir2 =~ s/\/?$//;
}
# dirname:
$dir1 =~ s/\/?[^\/]+$//;
$dir2 =~ s/\/?[^\/]+$//;
if($dir1 ne $dir2) {
warn "Non-basename directory portion changed:\n";
warn "$dir1 ->\n";
warn "$dir2.\n";
warn "Nothing moved.\n";
oops(@newf);
exit 1;
}
}
for ($n = 0; $n <= $#newf; $n++)
{
if($files[$n] eq $newf[$n]) { next; }
# Get a version of the filename without a trailing slash (if applicable).
$f = $files[$n];
if($f =~ /^.*\/$/) {
$f =~ /^(.*)\/$/;
$f = $1;
}
# Put the temporary name at the *end*, not the beginning;
# the beginning may include a path.
push @s, [$f, "$f.TMP.$$", $newf[$n]];
$verbose && print "$s[$#s][0] -> ";
$debug && print "$s[$#s][1] -> ";
$verbose && print "$s[$#s][2]\n";
}
if($#s == -1) {
print "No changes.\n";
exit 0;
}
# $s[][0] -> $s[][1] -> $s[][2]
for ($n = 0; $n <= $#s; $n++)
{
$from = $s[$n][0];
$interim = $s[$n][1];
$debug && print "$from -> $interim\n";
if(-e $interim) {
die "Interim filename $interim exists, giving up.\n";
}
rename $from, $interim || die "Couldn\'t rename $from to $interim: $!\n";
}
# do any dest files exist?
for ($n = 0; $n <= $#s; $n++) {
-e $s[$n][2] || next;
warn "Destination filename $s[$n][2] exists; giving up.\n";
# Put things back the way they were
for ($n = 0; $n <= $#s; $n++) {
$from = $s[$n][0];
$interim = $s[$n][1];
# If the original filename now exists, something is fishy
if(-e $from) {
die "Original filename $from exists, but was renamed. Giving up.\n";
}
rename $interim, $from || die "Couldn\'t rename $interim to $from: $!\n";
exit 1;
}
}
for ($n = 0; $n <= $#s; $n++)
{
$interim = $s[$n][1];
$to = $s[$n][2];
$debug && print "$interim -> $to\n";
if(-e $to) {
die "Destination filename $to exists; $from is now $interim.\n";
}
rename $interim, $to || warn "Couldn\'t rename $interim to $to: $!\n";
}
sub oops() {
open OOPS, ">broken-file-list-$$";
foreach $y ( @_ )
{
print OOPS "$y\n";
}
close(OOPS);
warn "File saved as broken-file-list-$$.\n";
}
sub hasdupes() {
my @sorted = sort(@_);
my $dupes = 0;
while(my $s = shift(@sorted)) {
$s eq $sorted[0] && return 1;
}
return 0;
}
# global: @prevpaths
sub adddir() {
my $dir = $_[0];
$debug && print "adddir($dir)\n";
# If it's a dir, make sure it has a trailing /.
if(-d $dir && $dir !~ /\/$/) {
$dir = "$dir/";
}
# If we're recursing, don't include directory names in the list.
# Always recurse into "./".
if($dir ne "./" && !($recurse && -d $dir)) {
# If we're not matching dotfiles, ignore ".foo" and "xyz/.foo".
!$dotfiles && $dir =~ /[\/^]\./ && return;
# If it has a preceding "./", remove it.
$dir =~ s/^\.\///;
push @files, $dir;
if(! -d $dir) {
return;
}
}
$recurse || $dir eq "./" || return;
opendir(DIR, $dir) || die "Can't open dir $dir: $!\n";
foreach (readdir(DIR)) {
($_ eq "." || $_ eq "..") && next;
adddir("$dir$_");
}
closedir(DIR);
}
Reply to: