Created
March 23, 2013 19:31
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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