http://www.perlmonks.org?node_id=632437

Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Hi All,

I have a very simple problem which I am hoping to use Perl for, I would like to do a mass rename of files - but I am not sure how to do the regex on the filename string to do this:

ABC 435535.doc -> 435535.doc

Remove everything before (and including) the whitespace...

Any help would be greatly appreciated! Thanks John

Replies are listed 'Best First'.
Re: Simple Perl file rename
by andreas1234567 (Vicar) on Aug 14, 2007 at 10:12 UTC
    Combine the advice from jesuashok with File::Copy and you should have a working, perl only, portable solution:

    $ touch ABC\ 435535.doc $ touch DEF\ 985535.doc $ touch GHI\ 125535.doc $ ls 632437.pl ABC 435535.doc DEF 985535.doc GHI 125535.doc $ perl -l 632437.pl $ ls 125535.doc 435535.doc 632437.pl 985535.doc $ cat 632437.pl use strict; use warnings; use File::Copy; while(<DATA>) { my $from = $_; chomp $from; (my $to = $from) =~ s/.* //g; move($from, $to) or die(qq{failed to move $from -> $to}); } __DATA__ ABC 435535.doc DEF 985535.doc GHI 125535.doc
    --
    Andreas
Re: Simple Perl file rename
by johnlawrence (Monk) on Aug 14, 2007 at 10:18 UTC
    rename.pl:
    #!/usr/bin/perl use strict; use warnings; foreach $_ (@ARGV) { my $oldfile = $_; s/.* //g; rename($oldfile, $_); }
    ./rename.pl *.doc
Re: Simple Perl file rename
by Corion (Patriarch) on Aug 14, 2007 at 10:31 UTC

    Also see pmv, which does arbitrary Perl-code substitutions on filenames.

    Update: Fixed broken link to pmv, spotted by parv

Re: Simple Perl file rename
by jesuashok (Curate) on Aug 14, 2007 at 10:02 UTC

    use strict; my $file_name_string = 'ABC 435535.doc'; $file_name_string =~ s/.* //g; print ":$file_name_string:\n";


    i m possible
Re: Simple Perl file rename
by moritz (Cardinal) on Aug 14, 2007 at 10:04 UTC
    On my system (Debian Linux; I don't know what yours is) there is a rename command that accepts perl code:

    rename 's/^ABC //' *.doc

Re: Simple Perl file rename
by hilitai (Monk) on Aug 14, 2007 at 14:16 UTC
    For the more general case, there's Larry's filename fixer, from the Perl Cookbook (and I'm sure I've seen it in print elsewhere, too):
    #!/usr/bin/perl # -w switch is off bc HERE docs cause erroneous messages to be display +ed under Cygwin #From the Perl Cookbook, Ch. 9.9 # rename - Larry's filename fixer $help = <<EOF; Usage: rename expr [files] This script's first argument is Perl code that alters the filename (st +ored in \$_ ) to reflect how you want the file renamed. It can do thi +s because it uses an eval to do the hard work. It also skips rename c +alls when the filename is untouched. This lets you simply use wildcar +ds like rename EXPR * instead of making long lists of filenames. Here are five examples of calling the rename program from your shell: % rename 's/\.orig$//' *.orig % rename 'tr/A-Z/a-z/ unless /^Make/' * % rename '$_ .= ".bad"' *.f % rename 'print "$_: "; s/foo/bar/ if <STDIN> =~ /^y/i' * % find /tmp -name '*~' -print | rename 's/^(.+)~$/.#$1/' The first shell command removes a trailing ".orig" from each filename. The second converts uppercase to lowercase. Because a translation is u +sed rather than the lc function, this conversion won't be locale-awar +e. To fix that, you'd have to write: % rename 'use locale; $_ = lc($_) unless /^Make/' * The third appends ".bad" to each Fortran file ending in ".f", somethin +g a lot of us have wanted to do for a long time. The fourth prompts the user for the change. Each file's name is printe +d to standard output and a response is read from standard input. If t +he user types something starting with a "y" or "Y", any "foo" in the +filename is changed to "bar". The fifth uses find to locate files in /tmp that end with a tilde. It +renames these so that instead of ending with a tilde, they start with + a dot and a pound sign. In effect, this switches between two common +conventions for backup files EOF $op = shift or die $help; chomp(@ARGV = <STDIN>) unless @ARGV; for (@ARGV) { $was = $_; eval $op; die $@ if $@; rename($was,$_) unless $was eq $_; }
    (Notice that about 90% of that is comments and usage.)

    Using that script, you can say:

    rename 's/ABC //' *.doc
    
    to rename all files with 'ABC ' in the name.
      Hiya,

      I am trying to understand Larry's filename fixer. I saw it first in the cookbook and am now looking here to find help. I am new to Perl, but I think (I hope) I understand everything the script does. The only thing I don't get is how all the matching filenames end up in @ARGV. So if I call the rename script in a directory with three txt files like so:

      rename 's/foo/bar/' *.txt

      @ARGV would be an Array with four entries like this right?

      s/foo/bar/\n
      file1.txt\n
      fiel2.txt\n
      file3.txt\n

      after the shift that removes the 's/foo/bar/' I am then left with the @ARGV that contains just the file names. The script then loops through all of them and works it's magic, so far so good.

      What I do not understand is how the script determines which files to put into @ARGV. At which point is "*.txt" being evaluated? Or is it the shell that tells the script which files in the directory match the pattern *.txt?

      This is probably a daft question but any help is much appreciated, as I said I am very new to this.


      Ta Arian

        In any UNIX-like shell, when you specify a filename glob (i.e. a filename with wildcards) on the shell commandline, it is the shell, and not the program you are calling, which expands the glob.

        So yes, when you call

        rename.pl *.txt
        the shell expands the *.txt to all matching files, and the Perl script finds the already expanded args in its @ARGV.

        If you want the script to do the glob expansion, you'd have to enclose the argument in single quotes, i.e. call it like this:

        somescript.pl '*.txt'

        Then the Perl script finds exactly one arg in @ARGV, namely *.txt and you would have to find some way to do the expansion.

        With Larry's script however, I typically feed it ALL the files by matching *; it will only act on those files which max the regexp given as the first argument anyway, all others are skipped. Of course that requires some care in constructing the regexp.

        @ARGV would be an Array with four entries like this right?
        Yes, except for the new lines, \n. Prove this to yourself with a test script:
        use strict; use warnings; print "These are the ", scalar @ARGV, " arguments:\n"; my $i = 0; for (@ARGV) { print "ARGV[$i] = $_\n"; $i++; }

        prints:

        These are the 4 arguments: ARGV[0] = s/foo/bar/ ARGV[1] = file1.txt ARGV[2] = file2.txt ARGV[3] = file3.txt

        Alternately, you can use Data::Dumper:

        use strict; use warnings; use Data::Dumper; print Dumper(\@ARGV);

        prints:

        $VAR1 = [ 's/foo/bar/', 'file1.txt', 'file2.txt', 'file3.txt' ];
        What I do not understand is how the script determines which files to put into @ARGV. At which point is "*.txt" being evaluated? Or is it the shell that tells the script which files in the directory match the pattern *.txt?
        The shift built-in function removes element 0 from the @ARGV array, shifting the remaining elements [1:3] down to new positions [0:2]. Again, here is a simple test script:
        use strict; use warnings; my $op = shift; # same as: my $op = shift @ARGV; print "These are the ", scalar @ARGV, " arguments:\n"; my $i = 0; for (@ARGV) { print "ARGV[$i] = $_\n"; $i++; }

        prints:

        These are the 3 arguments: ARGV[0] = file1.txt ARGV[1] = file2.txt ARGV[2] = file3.txt
Re: Simple Perl file rename
by girarde (Hermit) on Aug 14, 2007 at 13:46 UTC
    It would help to know the OS here, but the REGEX can be as simple as:
    $filename =~ s/.* ([^ ]*)$/$1/
Re: Simple Perl file rename
by agianni (Hermit) on Aug 14, 2007 at 15:51 UTC

    If all of your files are in a directory or hierarchy, here's a nice one-liner using File::Find from the command line:

    perl -MFile::Find -e 'find sub{ rename $_, (m/.*\s(.*)\z/)[0] }, q{.}'

    It's a little obfuscated, but the only part that might be confusing is the pattern match, wrapped with parens with an array index, which simply returns the first result of the match.

    perl -e 'split//,q{john hurl, pest caretaker}and(map{print @_[$_]}(joi +n(q{},map{sprintf(qq{%010u},$_)}(2**2*307*4993,5*101*641*5261,7*59*79 +*36997,13*17*71*45131,3**2*67*89*167*181))=~/\d{2}/g));'