?
Solved

Perl recursive rename based on results of directory contents

Posted on 2010-11-19
5
Medium Priority
?
243 Views
Last Modified: 2012-06-21
Hi,
I'm trying to figure out how to rename a bunch of directories and files based on the name of another file inside that directory.
Example:

# ls -al
total 4
drwxrwxrwx 1 root root  160 2010-11-19 23:03 .
drwxrwxrwx 1 root root 4096 2010-11-19 23:31 ..
drwxrwxrwx 1 root root  504 2010-11-19 22:57 The Departed

# cd The\ Departed/
# ls -al
total 57
drwxrwxrwx 1 root root   504 2010-11-19 22:57 .
drwxrwxrwx 1 root root   160 2010-11-19 23:03 ..
-rwxrwxrwx 1 root root 19200 2010-11-19 22:58 cover.jpg
-rwxrwxrwx 1 root root   698 2008-12-31 15:25 folder.wid
-rwxrwxrwx 1 root root 29207 2007-07-26 22:49 folder.wig
-rwxrwxrwx 1 root root     1 2010-11-19 22:59 Some_file name with spaces or parenthesis or a period, etc.avi
-rwxrwxrwx 1 root root   104 2010-11-19 22:58 The Departed (2006) - imdb.nfo


I need to:
1. rename the .avi (or other media type -match an array?) to the same name as the .nfo file (keeping the correct file extension, of course).
2. rename the parent directory the same way.

So the result would be:
# ls -al
total 4
drwxrwxrwx 1 root root  160 2010-11-19 23:03 .
drwxrwxrwx 1 root root 4096 2010-11-19 23:31 ..
drwxrwxrwx 1 root root  504 2010-11-19 22:57 The Departed (2006)

# cd The\ Departed\ (2006)/
# ls -al
total 57
drwxrwxrwx 1 root root   504 2010-11-19 22:57 .
drwxrwxrwx 1 root root   160 2010-11-19 23:03 ..
-rwxrwxrwx 1 root root 19200 2010-11-19 22:58 cover.jpg
-rwxrwxrwx 1 root root   698 2008-12-31 15:25 folder.wid
-rwxrwxrwx 1 root root 29207 2007-07-26 22:49 folder.wig
-rwxrwxrwx 1 root root     1 2010-11-19 22:59 The Departed (2006).avi
-rwxrwxrwx 1 root root   104 2010-11-19 22:58 The Departed (2006) - imdb.nfo

Make sense?
I would also like to do it recursively and (hopefully) not screw it all up :-)
0
Comment
Question by:cdukes
  • 3
  • 2
5 Comments
 
LVL 27

Expert Comment

by:wilcoxon
ID: 34180160
I think this will do what you want.  I would suggest copying part of your directory structure and testing it there (in case it screws up something)...

If there are problems, let me know and I'll correct them later today.
#!/usr/local/bin/perl

use strict;
use warnings;
use File::Find ();

# Set the variable $File::Find::dont_use_nlink if you're using AFS,
# since AFS cheats.

# for the convenience of &wanted calls, including -eval statements:
use vars qw/*name *dir *prune/;
*name   = *File::Find::name;
*dir    = *File::Find::dir;
*prune  = *File::Find::prune;

# change/add extensions to the list to pick up (or exclude) more
my @types = (qw(avi mpg mpeg));

# Traverse desired filesystems
File::Find::finddepth({wanted => \&wanted}, $your_tree_root_path);
exit;

sub wanted {
    if (-d $dir) {
        opendir DIR, $dir or die "could not open dir $dir: $!";
        my @files = readdir DIR;
        closedir DIR;
        my @nfo = grep m{\.nfo$}, @files;
        my ($base, $path)
        if (@nfo > 1) {
            die "found multiple .nfo files in $dir - don't know what to do";
        } elsif (@nfo == 1) {
            ($base, $path) = fileparse($nfo[0], 'nfo');
            my $rx = join '|', @types;
            my @media = grep m{\.(?:$rx)$}, @files;
            die "found multiple media files (@media) in $dir - don't know what to do" if (@media > 1);
            my ($junk1, $junk2, $suf) = fileparse($media[0], @types);
            rename $media[0], "$path/$base.$suf"
                or die "could not rename $media[0] to $path/$base.$suf: $!";
        }
        my $pdir = $dir;
        $pdir =~ s{/[^/]+/?$}{};
        rename $dir, "$pdir/$base" or die "could not rename dir $dir to $pdir/$base: $!";
    }
}

Open in new window

0
 
LVL 27

Expert Comment

by:wilcoxon
ID: 34180163
Minor correction as I just noticed your example .nfo has " - imdb" but your renamed one does not...

Add this after line 33:

$base =~ s{\s+-\s+imdb$}{};
0
 

Author Comment

by:cdukes
ID: 34180730
Had to make a few slight changes, but here's what I get:
could not rename The.Departed  (2006).avi to .//The Departed    (2006) - imdb.avi: No such file or directory at ./p.pl line 42.

It looks like it's not detecting the full path of the .nfo file, just the file name itself?

New code below.
#!/usr/bin/perl

use strict;
use warnings;
use File::Find ();
use File::Basename;

# Set the variable $File::Find::dont_use_nlink if you're using AFS,
# since AFS cheats.

# for the convenience of &wanted calls, including -eval statements:
use vars qw/*name *dir *prune/;
*name   = *File::Find::name;
*dir    = *File::Find::dir;
*prune  = *File::Find::prune;

# change/add extensions to the list to pick up (or exclude) more
my @types = (qw(avi mpg mpeg));
my @searchdirs = (qw(.));

# Traverse desired filesystems
File::Find::finddepth({wanted => \&wanted}, @searchdirs);
exit;

sub wanted {
    my $dir = $_;
    if (-d $dir) {
        opendir DIR, $dir or die "could not open dir $dir: $!";
        my @files = readdir DIR;
        closedir DIR;
        my @nfo = grep m{\.nfo$}, @files;
        my ($base, $path);
        if (@nfo > 1) {
            die "found multiple .nfo files in $dir - don't know what to do";
        } elsif (@nfo == 1) {
            ($base, $path) = fileparse($nfo[0], 'nfo');
            $base =~ s{\s+-\s+imdb$}{};
            my $rx = join '|', @types;
            my @media = grep m{\.(?:$rx)$}, @files;
            die "found multiple media files (@media) in $dir - don't know what to do" if (@media > 1);
            my ($junk1, $junk2, $suf) = fileparse($media[0], @types);
            rename $media[0], "$path/$base$suf"
                or die "could not rename $media[0] to $path/$base$suf: $!";
        }
        my $pdir = $dir;
        $pdir =~ s{/[^/]+/?$}{};
        rename $dir, "$pdir/$base" or die "could not rename dir $dir to $pdir/$base: $!";
    }
}

Open in new window

0
 
LVL 27

Accepted Solution

by:
wilcoxon earned 2000 total points
ID: 34181308
Looks like it.  This should work now...
#!/usr/bin/perl

use strict;
use warnings;
use File::Find ();
use File::Basename;

# Set the variable $File::Find::dont_use_nlink if you're using AFS,
# since AFS cheats.

# for the convenience of &wanted calls, including -eval statements:
use vars qw/*name *dir *prune/;
*name   = *File::Find::name;
*dir    = *File::Find::dir;
*prune  = *File::Find::prune;

# change/add extensions to the list to pick up (or exclude) more
my @types = (qw(avi mpg mpeg));
#my @searchdirs = (qw(.));
my @searchdirs = (qw(test));

# Traverse desired filesystems
File::Find::finddepth({wanted => \&wanted}, @searchdirs);
exit;

sub wanted {
    my $dir = $_;
    if (-d $dir) {
        opendir DIR, $dir or die "could not open dir $dir: $!";
        my @files = readdir DIR;
        closedir DIR;
        my @nfo = grep m{\.nfo$}, @files;
        my $base;
        return unless @nfo;
        if (@nfo > 1) {
            die "found multiple .nfo files in $dir - don't know what to do";
        } else {
            ($base) = fileparse($nfo[0], 'nfo');
            $base =~ s{\s+-\s+imdb\.$}{};
            my $rx = join '|', @types;
            my @media = grep m{\.(?:$rx)$}, @files;
            die "found multiple media files (@media) in $dir - don't know what to do" if (@media > 1);
            my ($junk1, $junk2, $suf) = fileparse($media[0], @types);
            rename "$dir/$media[0]", "$dir/$base$suf"
                or die "could not rename $dir/$media[0] to $dir/$base.$suf: $!";
        }
        my $pdir = $dir;
        if ($pdir =~ m{/}) {
            $pdir =~ s{/[^/]+/?$}{};
        } else {
            $pdir = '.';
        }
        rename $dir, "$pdir/$base" or die "could not rename dir $dir to $pdir/$base: $!";
    }
}

Open in new window

0
 

Author Closing Comment

by:cdukes
ID: 34181848
Awesome, thank you SO much!
0

Featured Post

[Webinar] Kill tickets & tabs using PowerShell

Are you tired of cycling through the same browser tabs everyday to close the same repetitive tickets? In this webinar JumpCloud will show how you can leverage RESTful APIs to build your own PowerShell modules to kill tickets & tabs using the PowerShell command Invoke-RestMethod.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Attention: This article will no longer be maintained. If you have any questions, please feel free to mail me. jgh@FreeBSD.org Please see http://www.freebsd.org/doc/en_US.ISO8859-1/articles/freebsd-update-server/ for the updated article. It is avail…
Have you ever been frustrated by having to click seven times in order to retrieve a small bit of information from the web, always the same seven clicks, scrolling down and down until you reach your target? When you know the benefits of the command l…
This video shows how to set up a shell script to accept a positional parameter when called, pass that to a SQL script, accept the output from the statement back and then manipulate it in the Shell.
Six Sigma Control Plans
Suggested Courses

600 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question