简体   繁体   中英

Using Perl to rename files in a directory

I'd like to take a directory and for all email (*.msg) files, remove the 'RE ' at the beginning. I have the following code but the rename fails.

opendir(DIR, 'emails') or die "Cannot open directory";
@files = readdir(DIR);
closedir(DIR);

for (@files){
    next if $_ !~ m/^RE .+msg$/;
    $old = $_;
    s/RE //;
    rename($old, $_) or print "Error renaming: $old\n";
}

If your ./emails directory contains these files:

1.msg
2.msg
3.msg

then your @files will look something like ('.', '..', '1.msg', '2.msg', '3.msg') but your rename wants names like 'emails/1.msg' , 'emails/2.msg' , etc. So you can chdir before renaming:

chdir('emails');
for (@files) {
    #...
}

You'd probably want to check the chdir return value too.

Or add the directory names yourself:

rename('emails/' . $old, 'emails/' . $_) or print "Error renaming $old: $!\n";
# or rename("emails/$old", "emails/$_") if you like string interpolation
# or you could use map if you like map

You might want to combine your directory reading and filtering using grep :

my @files = grep { /^RE .+msg$/ } readdir(DIR);

or even this:

opendir(DIR, 'emails') or die "Cannot open directory";
for (grep { /^RE .+msg$/ } readdir(DIR)) {
    (my $new = $_) =~ s/^RE //;
    rename("emails/$_", "emails/$new") or print "Error renaming $_ to $new: $!\n";
}
closedir(DIR);

You seem to be assuming glob -like behavior rather than than readdir -like behavior.

The underlying readdir system call returns just the filenames within the directory, and will include two entries . and .. . This carries through to the readdir function in Perl, just to give a bit more detail on mu's answer.

Alternately, there's not much point to using readdir if you're collecting all the results in an array anyways.

@files = glob('emails/*');

As already mentioned, your script fails because of the path you expect and the script uses are not the same.

I would suggest a more transparent usage. Hardcoding a directory is not a good idea, IMO. As I learned one day when I made a script to alter some original files, with the hardcoded path, and a colleague of mine thought this would be a nice script to borrow to alter his copies. Ooops!

Usage:

perl script.pl "^RE " *.msg

ie regex, then a file glob list, where the path is denoted in relation to the script, eg *.msg , emails/*.msg or even /home/pat/emails/*.msg /home/foo/*.msg . (multiple globs possible)

Using the absolute paths will leave the user with no doubt as to which files he'll be affecting, and it will also make the script reusable.

Code:

use strict;
use warnings;
use v5.10;
use File::Copy qw(move);

my $rx = shift;   # e.g. "^RE "

if ($ENV{OS} =~ /^Windows/) {  # Patch for Windows' lack of shell globbing
    @ARGV = map glob, @ARGV;
}

for (@ARGV) {
    if (/$rx/) {
        my $new = s/$rx//r;  # Using non-destructive substitution
        say "Moving $_ to $new ...";
        move($_, $new) or die $!;
    }
}

I don't know if the regex fits the specifig name of the files, but in one line this could be done with:

perl -E'for (</path/to/emails*.*>){ ($new = $_) =~ s/(^RE)(.*$)/$2/; say $_." -> ".$new}

( say ... is nice for testing, just replace it with rename $_,$new or rename($_,$new) )

  1. <*.*> read every file in the current directory
  2. ($new = $_) =~ saves the following substitution in $new and leaves $_ as intact
  3. (^RE) save this match in $1 (optional) and just match files with "RE" at the beginning
  4. (.*$) save everything until and including the end ($) of the line -> into $2
  5. substitute the match with the string in $2

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM