Skip to content

Instantly share code, notes, and snippets.

@rae
Created March 23, 2013 19:31
Show Gist options
  • Save rae/5229069 to your computer and use it in GitHub Desktop.
Save rae/5229069 to your computer and use it in GitHub Desktop.
rn - (ReName) a Perl script for batch renaming files, with %-escapes for the file's modification date and time.
#!/usr/bin/perl
use Getopt::Long;
use POSIX qw(strftime);
my %options = {
capitalize => 0,
chronological => 0,
execute => 0,
recursive => 0,
help => 0,
helpPatterns => 0,
notreally => 0,
quietNotReally => 0,
sensitive => 0,
verbose => 0,
ignore => qw(.DS_Store)
};
%presets = {
capitalize => ['([_-])([a-z])', '$1\U$2\E']
};
@displays = ();
&GetOptions(
"--capitalize" => \$options{capitalize},
"--chorono" => \$options{chronological},
"--execute" => \$options{execute},
"--help" => \$options{help},
"--patterns-help" => \$options{helpPatterns},
"--notreally" => \$options{notreally},
"--quietNotReally" => \$options{quietNotReally},
"--recursive" => \$options{recursive},
"--sensitive" => \$options{sensitive},
"--verbose" => \$options{verbose},
);
my $progname = $0;
$progname =~ s#.*/##;
sub vb {
if($options{verbose}) {
print @_, "\n";
}
}
if($options{capitalize}) {
print "Capitalizing..\n";
push @ARGV, $presets{capitalize};
# push @ARGV, '([_-])([a-z])', '$1\U$2\E';
}
print "Recursive..\n" if $options{recursive};
# quietNotReally implies notReally
$options{notreally} = 1 if $options{quietNotReally};
if($options{help} || (@ARGV == 0 && !$options{helpPatterns})) {
print <<"EOF";
Usage: $progname [-c] [-r] [-h] pattern replacement
-ca/--capitalize
-ch/--chrono
-e/--execute
-r/--recursive
-h/--help
-n/--notreally
-p/--patterns-help
-q/--quietNotReally (just the results)
-s/--sensitive (be case-sensitive)
EOF
}
if($options{chronological}) {
vb "Sorting chronologically\n";
}
if($options{helpPatterns}) {
print << "EOF";
$progname understands the same %-escapes as those used in
strftime(2). Here is a summary of those escapes:
%A is replaced by the locale's full weekday name.
%a is replaced by the locale's abbreviated weekday name.
%B is replaced by the locale's full month name.
%b or %h
is replaced by the locale's abbreviated month name.
%C is replaced by the century (a year divided by 100 and truncated to
an integer) as a decimal number (00-99).
%c is replaced by the locale's appropriate date and time representa-
tion.
%D is replaced by the date in the format ``%m/%d/%y''.
%d is replaced by the day of the month as a decimal number (01-31).
%e is replaced by the day of month as a decimal number (1-31); single
digits are preceded by a blank.
%H is replaced by the hour (24-hour clock) as a decimal number
(00-23).
%I is replaced by the hour (12-hour clock) as a decimal number
(01-12).
%j is replaced by the day of the year as a decimal number (001-366).
%k is replaced by the hour (24-hour clock) as a decimal number (0-23);
single digits are preceded by a blank.
%l is replaced by the hour (12-hour clock) as a decimal number (1-12);
single digits are preceded by a blank.
%M is replaced by the minute as a decimal number (00-59).
%m is replaced by the month as a decimal number (01-12).
%n is replaced by a newline.
%p is replaced by the locale's equivalent of either ``AM'' or ``PM''.
%R is replaced by the time in the format ``%H:%M''.
%r is replaced by the locale's representation of 12-hour clock time
using AM/PM notation.
%T is replaced by the time in the format ``%H:%M:%S''.
%t is replaced by a tab.
%S is replaced by the second as a decimal number (00-60).
%s is replaced by the number of seconds since the Epoch, UCT (see
mktime(3)).
%U is replaced by the week number of the year (Sunday as the first day
of the week) as a decimal number (00-53).
%u is replaced by the weekday (Monday as the first day of the week) as
a decimal number (1-7).
%V is replaced by the week number of the year (Monday as the first day
of the week) as a decimal number (01-53). If the week containing
January 1 has four or more days in the new year, then it is week 1;
otherwise it is week 53 of the previous year, and the next week is
week 1.
%W is replaced by the week number of the year (Monday as the first day
of the week) as a decimal number (00-53).
%w is replaced by the weekday (Sunday as the first day of the week) as
a decimal number (0-6).
%X is replaced by the locale's appropriate time representation.
%x is replaced by the locale's appropriate date representation.
%Y is replaced by the year with century as a decimal number.
%y is replaced by the year without century as a decimal number
(00-99).
%Z is replaced by the time zone name.
%% is replaced by `%'.
In addition, $progname adds:
%i is replaced by the current index number [1, 2, 3.. etc]
EOF
}
if($options{help} || $options{helpPatterns}) {
exit 0;
}
$patt = shift;
if(${patt} eq ".*") {
$patt = "^.*\$";
}
$repl = shift;
$repl = "" unless $repl;
vb "repl was \"$repl\"";
($repl_i = $repl) =~ s/%([0-9.]*[dsf])/\0${1}/g;
$repl_i =~ s/%([0-9.]*)i/%${1}d/g;
vb "repl is now \"$repl\" and repl_i is \"$repl_i\"";
# in case mv supports backups
$ENV{VERSION_CONTROL} = "numbered";
@files = @ARGV;
if(!@files) {
opendir(DIR, ".") || die "Can't read dir '.': $!\n";
@files = sort readdir(DIR);
closedir(DIR);
}
&rename_files(\@files);
$maxOrigLength = 0;
foreach $e (@displays) {
$l = length($e->[0]);
$maxOrigLength = $l if $l > $maxOrigLength;
}
foreach $e (@displays) {
printf("mv %-${maxOrigLength}s %s\n", $e->[0], $e->[1]);
}
sub is_in($@) {
my $str = shift;
my @arr = shift;
my $found = 0;
map { $found++ if $_ eq $str } @arr;
return $found;
}
sub rename_files {
my $filesArr = shift;
local *DIR;
my @dirs;
my $index=1;
my $indexedRepl;
my $file;
my $printFile;
my $newfile;
my @statinfo;
if($options{chronological}) {
vb "before chrono sort:\n\t";
vb join(", ", @$filesArr);
vb "\n";
# sort files by modification time
@$filesArr = sort { -M $b <=> -M $a } @$filesArr;
vb "after chrono sort:\n\t";
vb join(", ", @$filesArr);
vb "\n";
}
if($options{sensitive}) {
vb "case sensitive";
} else {
vb "case insensitive";
}
foreach $file (@$filesArr) {
next if $file eq ".";
next if $file eq "..";
next if $file eq ".DS_Store";
#next if is_in($file, $options{ignore});
my $found=0;
map { $found++ if $_ eq $file } $options{ignore};
next if $found;
if($options{recursive} && -d $file) {
push @dirs, $file;
}
if($options{sensitive}) {
next unless $file =~ /${patt}/;
} else {
next unless $file =~ /${patt}/i;
}
vb "found \"$file\"; calling sprintf(\"$repl_i\", $index)";
vb "indexedRepl was \"$indexedRepl\"";
$indexedRepl = sprintf($repl_i, $index++);
vb "indexedRepl is now \"$indexedRepl\"";
$indexedRepl =~ s/\0/%/g;
vb "indexedRepl is finally \"$indexedRepl\"";
@statinfo = stat($file);
($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
$atime,$mtime,$ctime,$blksize,$blocks) = stat($filename);
$indexedRepl = strftime $indexedRepl, localtime($statinfo[9]);
vb "substituting \"${patt}\" with \"${indexedRepl}\" ";
vb "newfile = $file";
$newfile = $file;
$g="g";
if(!$options{sensitive}) {
$g .= "i";
}
if($options{execute}) {
$g .= "e";
}
vb "\$newfile =~ s/${patt}/${indexedRepl}/${g};";
eval "\$newfile =~ s/${patt}/${indexedRepl}/${g};";
vb "newfile is \"$newfile\"";
($fileDisplay = $file) =~ s/([ '&\(\)\\\[\]])/\\$1/ig;
($newfileDisplay = $newfile) =~ s/([ '&\(\)\\\[\]])/\\$1/ig;
if($newfile eq "") {
$newfile = "untitled";
}
if($file ne $newfile) {
$base = $newfile;
$ext = "";
if($newfile =~ /(.*)(\.[^.])+$/) {
$base=$1;
$ext=$2;
}
# look for where to rename old file
for($i=1; $i<100000 && -e "${base}~$i${ext}"; $i++) {
;
}
$oldfile = "${base}~$i${ext}";
if($options{sensitive}) {
($oldfileDisplay = $oldfile) =~ s/([ '&\(\)\\\[\]])/\\$1/g;
} else {
($oldfileDisplay = $oldfile) =~ s/([ '&\(\)\\\[\]])/\\$1/ig;
}
if($options{quietNotReally}) {
print "$newfileDisplay\n";
} else {
# add to the list of files to be displayed nicely
push @displays, [$fileDisplay, $newfileDisplay];
}
unless($options{notreally}) {
rename $newfile, $oldfile if(-e $newfile && not -e $oldfile);
if(-e $newfile && not -e $oldfile) {
warn "${progname}: backing up to \"${oldfile}\"\n";
rename $newfile, $oldfile || warn "Unable to rename \"${newfileDisplay}\" to \"${oldfileDisplay}\": $!\n";
}
rename $file, $newfile || warn "Unable to rename \"$fileDisplay\" to \"$newfileDisplay\": $!\n";
}
} else {
print "\"$fileDisplay\" is the same as \"$newfileDisplay\"\n";
}
if($options{recursive} && -d $newfile) {
# get rid of old name
pop @dirs;
# new name
push @dirs, $file;
}
}
if($options{recursive}) {
my $cwd = `pwd`;
my @files;
chop $cwd;
foreach $dir(@dirs) {
if(!opendir(DIR, $dir)) {
warn "Can't read dir \"$cwd/$dir\": $!\n";
} else {
print "\t$cwd/$dir\n";
@files = sort readdir(DIR);
closedir(DIR);
chdir($dir);
rename_files(\@files);
chdir($cwd);
closedir(DIR);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment