user2585169
user2585169

Reputation: 23

uniquely rename each of many files using perl

I have a folder containing 96 files that I want to rename. The problem is that each file name needs a unique change...not like adding a zero the front of each name or changing extensions. It isn't practical to do a search and replace. Here's a sample of the names I want to change:

newSEACODI-sww2320H-sww24_07A_CP.9_sww2320H_sww2403F.fsa
newSEACODI-sww2320H-sww24_07B_CP.10_sww2320H_sww2403F.fsa
newSEACODI-sww2320H-sww24_07C_CP.11_sww2320H_sww2403F.fsa
newSEACODI-sww2320H-sww24_07D_CP.12_sww2320H_sww2403F.fsa
newSEACODI-sww2320H-sww24_07E_R.1_sww2320H_sww2403F.fsa
newSEACODI-sww2320H-sww24_07F_R.3_sww2320H_sww2403F.fsa
newSEACODI-sww2320H-sww24_07G_R.4_sww2320H_sww2403F.fsa
newSEACODI-sww2320H-sww24_07H_R.5_sww2320H_sww2403F.fsa

I'd like to use perl to change the above names to the below names, respectively:

SEACODI_07A_A.2_sww2320H_2403F.fsa
SEACODI_07B_A.4_sww2320H_2403F.fsa
SEACODI_07C_H.1_sww2320H_2403F.fsa
SEACODI_07D_H.3_sww2320H_2403F.fsa
SEACODI_07E_H.6_sww2320H_2403F.fsa
SEACODI_07F_H.7_sww2320H_2403F.fsa
SEACODI_07G_Rb.4_sww2320H_2403F.fsa
SEACODI_07H_Rb.9_sww2320H_2403F.fsa

Can such a thing be done? I have a vague idea that I might make a text file with a list of the new names and call that list @newnames. I would make another array out of the current file names, and call it @oldnames. I'd then do some kind of for loop where each element $i in @oldnames is replaced by the corresponding $i in @newnames.

I don't know how to make an array out of my current file names, though, and so I'm not sure if this vague idea is on the right track. I keep my files with the messed-up names in a directory called 'oldnames'. The below is my attempt to make an array out of the file names in that directory:

#!/usr/bin/perl -w
use strict; use warnings;

my $dir = 'oldnames';
opendir ('oldnames', $dir) or die "cannot open dir $dir: $!";
my @file = readdir 'oldnames';
closedir 'oldnames';

print "@file\n";

The above didn't seem to do anything. I'm lost. Help?

Upvotes: 2

Views: 1964

Answers (3)

G. Cito
G. Cito

Reputation: 6378

Can you do this with rename? It does allow you to use perl code and expressions as arguments if I recall.

The real answer is the one by @chrsblck it does some checks and doesn't make a mess.

For comparison here is a messy one liner that may suffice. It relies on you providing a list of equivalent new file names that will rename your list of old files in the correct order. Perhaps for your situation (where you don't want to do any programmatic transformation of the files names) you could just use a shell loop (see the end of this post) reading lists of new and old names from a file. A better perl solution would be to put both of these file name lists into two columns and then that file using the -a switch , @F and then useFile::Copy to copy the files around.

Anyway, below are some suggestions.

First, set things up:

 % vim newfilenames.txt # list new names one per line corresponding to old names.
 % wc -l newfilenames.txt # the same number of new names as files in ./oldfiles/
  8 newfilenames.txt
 % ls -1 oldfiles  # 8 files rename these in order to list from newfilenames.txt
 newSEACODI-sww2320H-sww24_07A_CP.9_sww2320H_sww2403F.fsa
 newSEACODI-sww2320H-sww24_07B_CP.10_sww2320H_sww2403F.fsa
 newSEACODI-sww2320H-sww24_07C_CP.11_sww2320H_sww2403F.fsa
 newSEACODI-sww2320H-sww24_07D_CP.12_sww2320H_sww2403F.fsa
 newSEACODI-sww2320H-sww24_07E_R.1_sww2320H_sww2403F.fsa
 newSEACODI-sww2320H-sww24_07F_R.3_sww2320H_sww2403F.fsa
 newSEACODI-sww2320H-sww24_07G_R.4_sww2320H_sww2403F.fsa
 newSEACODI-sww2320H-sww24_07H_R.5_sww2320H_sww2403F.fsa

With files arranged as above, copy everything over:

perl -MFile::Copy -E 'opendir($dh , oldfiles); @newfiles=`cat newfilenames.txt`; chomp @newfiles; @oldfiles = sort grep(/^.+\..+$/, readdir $dh); END {for $i (0..$#oldfiles){copy("oldfiles/$oldfiles[$i]", "newfiles/$newfiles[$i]"); }}'

Not pretty: you have to grep andsort on @oldfiles to get rid of . .. and put the array elments in order. And there's always the risk that a typo could make a mess and it would be hard to figure out.
If you put the old and new names in a couple of files you could just do this with this with a shell script:

for i in `cat ../oldfilenames.txt` ; do ; done; for n in `cat ../newfilenames.txt`; do cp $i $n;

or just cd into the directory with the old files and do:

mkdir new
for i in * ; do ; done; for n in `cat ../newfilenames.txt`; do cp $i new/$n;

Good luck!

Upvotes: 0

chrsblck
chrsblck

Reputation: 4088

Here:

#!/usr/bin/perl
use warnings;
use strict;

use autodie;
use File::Copy;

# capture script name, in case we are running the script from the 
# same directory we working on. 
my $this_file = (split(/\//, $0))[-1];
print "skipping file: $this_file\n";

my $oldnames = "/some/path/to/oldnames";
my $newnames = "/some/path/to/newnames";

# open the directory
opendir(my $dh, $oldnames); 

# grep out all directories and possibly this script. 
my @files_to_rename = grep { !-d && $_ ne $this_file } readdir $dh;
closedir $dh;

### UPDATED ###
# create hash of file names from lists:
my @old_filenames = qw(file1 file2 file3 file4);
my @new_filenames = qw(onefile twofile threefile fourfile);
my $filenames = create_hash_of_filenames(\@old_filenames, \@new_filenames);
my @missing_new_file = ();  


# change directory, so we don't have to worry about pathing
# of files to rename and move... 
chdir($oldnames);
mkdir($newnames) if !-e $newnames;


### UPDATED ###
for my $file (@files_to_rename) {
    # Check that current file exists in the hash,
    # if true, copy old file to new location with new name
    if( exists($filenames->{$file}) ) { 
        copy($file, "$newnames/$filenames->{$file}");
    } else {
        push @missing_new_file, $file;
    } 
}


if( @missing_new_file ) { 
    print "Could not map files:\n", 
        join("\n", @missing_new_file), "\n";
}

# create_hash_of_filenames: creates a hash, where
# key = oldname, value = newname
# input: two array refs 
# output: hash ref 
sub create_hash_of_filenames {
    my ($oldnames, $newnames) = @_; 
    my %filenames = (); 

    for my $i ( 0 .. (scalar(@$oldnames) - 1) ) { 
        $filenames{$$oldnames[$i]} = $$newnames[$i];
    }

    # see Dumper output below, to see data structure
    return \%filenames;
}

Dumper result:

$VAR1 = {
  'file2' => 'twofile',
  'file1' => 'onefile',
  'file4' => 'fourfile',
  'file3' => 'threefile'
};

Running script:

$ ./test.pl 
skipping file: test.pl
Could not map files:
a_file.txt
b_file.txt
c_file.txt

File result:

$ ls oldnames/
a_file.txt
b_file.txt
c_file.txt
file1
file2
file3
file4

$ ls newnames/
fourfile
onefile
threefile
twofile

Upvotes: 1

dms
dms

Reputation: 817

Your code is a little odd, but it should work. Are you running it in the directory "oldnames" or in the directory above it? You should be in the directory above it. A more standard way of writing it would be like this:

#!/usr/bin/perl -w
use strict; use warnings;

my $dir = 'oldnames';
opendir ( my $oldnames, $dir) or die "cannot open dir $dir: $!";
my @file = readdir $oldnames;
closedir $oldnames;

print "@file\n";

This would populate @files with all the files in oldnames, including '.' and '..'. You might need to filter those out depending on how you do your renaming.

Upvotes: 0

Related Questions