[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

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: