Inject linux command 'rename' with Perl
Mac Os X does not have a useful linux command rename
which has the following format:
rename 'perl-regex' list-of-files
So, this is what I put together, but it doesn't rename any files ($ new is always the same as $ file):
#!/usr/bin/env perl -w
use strict;
use File::Copy 'move';
my $regex=shift;
my @files=@ARGV;
for my $file (@files)
{
my $new=$file;
$new =~ "$regex"; # this is were the problem is !!!
if ($new ne $file)
{
print STDOUT "$file --> $new \n";
move $file, ${new} or warn "Could not rename $file to $new";
}
}
As if I am not passing a regex and if I copy it
$new =~ s/TMP/tmp;
it will work perfectly ... Any thoughts?
source to share
$operator = 's/TMP/tmp/';
print $operator;
does not magically evaluate the operator, so it should come as no surprise that
$operator = 's/TMP/tmp/';
$x =~ $operator;
also. If you want to evaluate Perl code, you have to pass it to the Perl interpreter. You can access it with eval EXPR
.
$operator = 's/TMP/tmp/';
eval('$x =~ '.$operator.'; 1')
or die $@;
source to share
You cannot put the entire sentence s/TMP/tmp;
into a variable. You can, however, do something like
$new =~ s/$find/$replace;
$find
is your regular expression and $replace
what you want to replace with matches.
If you still want to pass the whole sentence, you can take a look at eval () .
source to share
There are two ways that can be solved elegantly
-
Require two separate command line arguments: one for regex and one for replacement. It's inelegant and restrictive.
my ($search, $replace, @files) = @ARGV; ...; my $new = $file; $new =~ s/$search/$replace/e; # the /e evals the replacement, # allowing us to interpolate vars
Called as
my-rename '(.*)\.txt' '@{[our $i++]}-$1.foo' *.txt
. This allows you to execute almost any code ¹⁾ through string variable interpolation.(1): no nested regex in older perls
-
Just allow arbitrary Perl code like
perl -ne'...'
. The semantics of the switch-n
is that the current line is passed as$_
. It would be wise to pass the filenames as$_
and use the value of the last statement as the new filename. This will result in something like# somewhat tested my ($eval_content, @files) = @ARGV; my $code = eval q' sub { no strict; # could be helpful ;-) my @_out_; FILENAME: for (@_) { my $_orig_ = $_; push @_out_, [ $_orig_ => do { ' . $eval_content . q' } ]; # or # do { " . $eval_content . " }; # push @_out_, [ $_orig_, $_ ]; # if you want to use $_ as out-argument (like -p). # Can lead to more concise code. } return @_out_; } '; die "Eval error: $@" if $@; for my $rename ($code->(@files)) { my ($from, $to) = @$rename; ... }
It can be called like
my-rename 'next FILENAME if /^\./; our $i++; s/(.*)\.txt/$i-$1.foo/; $_' *.txt
. This skips all files starting with a period, registers a global variable,$i
and puts a number up from one in front of each filename and changes the extension. We then return$_
in the last statement.The loop builds pairs of original and new filenames that can be processed in the second loop.
It's probably pretty flexible and not overly inefficient.
source to share
Well, this is already a Perl utility and it's on CPAN: http://cpan.me/rename . You can use the module that comes with this utility, File :: Rename , directly:
#!/usr/bin/env perl
use File::Rename qw(rename);
rename @ARGV, sub { s/TMP/tmp/ }, 'verbose';
Another possibility is to combine the module and script with that distribution and put the resulting file somewhere in yours $PATH
.
source to share