Using Perl to rename files in a directory

前端 未结 4 403
故里飘歌
故里飘歌 2021-01-12 10:56

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(D         


        
相关标签:
4条回答
  • 2021-01-12 11:03

    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
    

    i.e. regex, then a file glob list, where the path is denoted in relation to the script, e.g. *.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 $!;
        }
    }
    
    0 讨论(0)
  • 2021-01-12 11:03

    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
    0 讨论(0)
  • 2021-01-12 11:04

    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/*');
    
    0 讨论(0)
  • 2021-01-12 11:30

    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);
    
    0 讨论(0)
提交回复
热议问题