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?

+3


source to share


4 answers


$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 $@;

      

+3


source


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 () .

+2


source


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.

+2


source


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

.

+2


source







All Articles