Script to rename files with the name of the folder? - perl

I want to recursively rename files by prepending the folder name in front of it. Ex: c:\test\foo\a.txt would become c:\test\foo\foo-a.txt.

The following Perl script may work for you:
#!/usr/bin/env perl
use strict;
use warnings;
use File::Basename;
use File::Find;
use File::Spec;
sub rename {
my ($dir_name) = ( File::Spec->splitdir($File::Find::dir) )[-1];
my $file_name = basename $_;
if ( -f $_ ) {
$file_name = "$dir_name-$file_name";
rename $_, File::Spec->catdir( $File::Find::dir, $file_name );
}
}
find { 'wanted' => \&rename, 'no_chdir' => 1 }, 'C:/test/foo';
References:
File::Basename
File::Find
File::Spec

use File::Find::Rule qw( );
use Path::Class qw( dir file );
my $base = dir('.')->absolute;
for my $qfn (File::Find::Rule->file->in($base)) {
my $file = file($qfn);
my $dir = $file->dir;
my $src = $file;
my $dst = $dir->file($dir->basename . '-' . $file->basename);
if (-e $dst) {
warn("Can't rename $src to $dst: Already exists\n");
}
elsif (!rename($src, $dst)) {
warn("Can't rename $src to $dst: $!\n");
}
}

use strict;
use warnings;
use File::Spec;
use File::Copy qw(move);
use File::Glob qw(:glob);
my $folder_path = qw( c:\test\foo\ );
my #file_paths = bsd_glob( $folder_path . '*' );
foreach my $old_path (#file_paths) {
if ( -f $old_path ) {
my ( $volume, $directories, $file ) = File::Spec->splitpath($old_path);
my #dirs = File::Spec->splitdir($directories);
my $prepend;
while ( !( $prepend = pop #dirs ) ) { } # see notes below
my $new_fname = $prepend . '-' . $file;
my $new_path = File::Spec->catpath( $volume, $directories, $new_fname );
move( $old_path, $new_path );
}
}
I use while to pop the last directory name before the filename because splitdir has a caveat on Windows. It looks like you are dealing with a Windows file.

#!/usr/bin/perl
use strict;
use warnings;
use File::Basename;
use File::Find;
use File::Spec;
($#ARGV == 0) or die "Usage: $0 [directory]\n";
my $input_file_dir = $ARGV[0];
sub process_file
{
my $dir_name = (File::Spec -> splitdir ($File::Find::dir)) [-1];
my $file_name = basename $_;
my $extension = ($file_name =~ m/([^.]+)$/)[0];
if ( -f $_ )
{
print "$dir_name.$extension\n";
rename $_, "$dir_name.$extension";
}
}
finddepth { 'wanted' => \&process_file, 'no_chdir' => 0 }, $input_file_dir;

Related

Bulk rename and move files in Perl

I’m searching for a way to rename each found .seg file to include the name of a folder two directories above the .seg file.
For example I found a .seg file in
/data/test_all_runs/TestRun/Focus-HD753/QC/diffCoverage.seg
and would like to rename it
Focus-HD753.seg
Once I renamed the file I would like to move it to
/data/test_all_runs/TestRun
or $ARGV[0]. Here is my current code:
#!/usr/bin/perl
use warnings;
use strict;
use File::Find;
use File::Spec;
my $home = "/data";
my #location_parts = ($home, 'test_all_runs');
push #location_parts, $ARGV[0] if #ARGV;
my $location = File::Spec->catdir(#location_parts);
my #moves;
my #vcf_moves;
sub find_seg {
my $F = $File::Find::name;
if ($F =~ /\.seg$/ ) {
my #path_parts = File::Spec->splitdir($F);
my $name = $path_parts[-3];
my $target = File::Spec->catdir($location, "$name.seg"); print $target;
push #moves, [ $F, $target ];
}
}
find({ wanted => \&find_seg, no_chdir => 1 }, $home);
while (#moves) {
my ($F, $target) = #{ shift #moves };
warn "$F -> $target";
rename $F, $target or warn "Can't move to $target";
}
sub find_vcf {
my $V = $File::Find::name;
if ($V =~ /(vcf$|oncomine\.tsv$)/ ) {
my #path_parts = File::Spec->splitdir($V);
print "The path_parts at 0 is #############".$path_parts[0]."\n";
print "The path_parts at -1 is #############".$path_parts[-1]."\n";
print "The path_parts at -2 is #############".$path_parts[-2]."\n";
print "The path_parts at -3 is #############".$path_parts[-3]."\n";
print "The path_parts at 1 is #############".$path_parts[1]."\n";
my $target_vcf = File::Spec->catdir($location, $path_parts[-1]); print $target_vcf;
push #vcf_moves, [ $V, $target_vcf ];
print "$V\n";
}
}
find({ wanted => \&find_vcf, no_chdir=>1}, $home);
while (#vcf_moves) {
my ($V, $target_vcf) = #{ shift #vcf_moves };
warn "$V -> $target_vcf";
rename $V, $target_vcf or warn "Can't move to $target_vcf";
}
Use rename to move a file to a name destination and name.
File::Spec makes the code OS independent. You can also check Path::Tiny for similar tasks.
The moves are saved in an array and excuted later, otherwise File::Find might move the same file several times as it walks the directories.
#!/usr/bin/perl
use warnings;
use strict;
use File::Find;
use File::Spec;
my $home = "/data";
my #location_parts = ($home, 'test_all_runs', 'TestRun');
push #location_parts, $ARGV[0] if #ARGV;
my $location = File::Spec->catdir(#location_parts);
my #moves;
sub find_seg {
my $F = $File::Find::name;
if ($F =~ /\.seg$/ ) {
my #path_parts = File::Spec->splitdir($F);
my $name = $path_parts[-3];
my $target = File::Spec->catdir($location, "$name.seg");
push #moves, [ $F, $target ];
}
}
find({ wanted => \&find_seg, no_chdir => 1 }, $home);
while (#moves) {
my ($F, $target) = #{ shift #moves };
warn "$F -> $target";
rename $F, $target or warn "Can't move to $target";
}
For the name file name modifications you could use File::Basename module, which is part of the core.
#!/usr/bin/env perl
use strict;
use warnings;
use feature qw{say};
use File::Basename;
my $filePath = "/data/test_all_runs/TestRun/Focus-HD753/QC/diffCoverage.seg";
my ($filename, $directories, $extension) = fileparse($filePath, '.seg');
my $target = (split m{/}, $directories)[-2];
say "Target: $target";
my $newFilePath = "$directories$target$extension";
say $newFilePath;
Result:
Target: Focus-HD753
/data/test_all_runs/TestRun/Focus-HD753/QC/Focus-HD753.seg
Then you can move it using $ARGV[0] to the location you want (maybe with File::Copy ?)

How to find the class-file on case insensitive filesystem?

Simple test case (for the demonstration of the problem):
mkdir -p ./lib1/Class ./lib2/Class
touch ./lib1/Class/Name.pm ./lib2/Class/NAME.pm
So, have:
./lib1/Class/Name.pm
./lib2/Class/NAME.pm
Need search for the right file in the case-insensitive filesystem (OS X's HFS+).
The following works on case-sensitive filesystem,
#!/usr/bin/env perl
use 5.014;
use strict;
use warnings;
my #DIRS = qw(./lib1 ./lib2);
for my $class ( qw(Class::Name Class::NAME) ) {
my $file = findClassFile($class);
say $file;
}
sub findClassFile {
my($file) = #_;
$file =~ s|::|/|g;
$file .= ".pm";
for my $dir (#DIRS) {
return "$dir/$file" if( -e "$dir/$file" );
}
return undef;
}
and prints
./lib1/Class/Name.pm
./lib2/Class/NAME.pm
on the OS X, it prints incorrectly:
./lib1/Class/Name.pm
./lib1/Class/NAME.pm
How to find on the OSX's insensitive filesystem the correct filename?
Ps: Now only comes to my mind write and recursive routine with opendir/readdir/chdir and checking the filenames what are comes from readdir. Not to shabby... Exists some more easy way?
My current solution is:
#!/usr/bin/env perl
use 5.014;
use strict;
use warnings;
my #DIRS = qw(./lib1 ./lib2 /Users/me/tmp/lib3);
for my $class ( qw(Class::Name Class::NAME CLASS::name Class::Namex) ) {
my $file = findClassFile($class);
say $file // "Not found $class";
}
sub findClassFile {
my($classname) = #_;
my $file = ($classname =~ s|::|/|gr) . ".pm";
for my $dir (#DIRS) {
return "$dir/$file" if( FileExists("$dir/$file") );
}
return undef;
}
sub FileExists {
my($path) = #_;
my $curr = $path =~ m|^/| ? "/" : ".";
for my $part (split '/', $path) {
next unless $part;
opendir(my $dfd, $curr) || return undef;
my #files = grep {/^$part$/} readdir($dfd);
closedir($dfd);
return undef unless( #files );
$curr .= "/$part";
}
return $curr;
}
what prints:
./lib1/Class/Name.pm
./lib2/Class/NAME.pm
/Users/me/tmp/lib3/CLASS/name.pm
Not found Class::Namex
so - it's working, only don't like it.. ;)

Unable to find it out duplicate - perl

I am traversing all files to get the desired one in some directory tree recursively, as soon as i am getting that files i doing some operation on them but before doing the operation i need to check whether i have performed operation on this file or not if yes then don't do it again else continue :
But the prob is, i am unable to find the way to check the condition :(
Here is my code :
use strict;
use warnings;
use autodie;
use File::Find 'find';
use File::Spec;
use Data::Printer;
my ( $root_path, $id ) = #ARGV;
our $anr_name;
opendir my ($dh), $root_path;
my #dir_list = grep -d, map File::Spec->catfile( $root_path, $_ ), grep { not /\A\.\.?\z/ } readdir $dh;
closedir $dh;
my $count;
for my $dir (#dir_list) {
find(
sub {
return unless /traces[_d]*/;
my $file = $_;
my #all_anr;
#print "$file\n\n";
my $file_name = $File::Find::name;
open( my $fh, "<", $file ) or die "cannot open file:$!\n";
my #all_lines = <$fh>;
my $i = 0;
foreach my $check (#all_lines) {
if ( $i < 10 ) {
if ( $check =~ /Cmd line\:\s+com\.android\..*/ ) {
$anr_name = $check;
my #temp = split( ':', $anr_name );
$anr_name = $temp[1];
push( #all_anr, $anr_name );
#print "ANR :$anr_name\n";
my $chk = check_for_dublicate_anr(#all_anr);
if ( $chk eq "1" ) {
# performed some action
}
}
$i++;
} else {
close($fh);
last;
}
}
},
$dir
);
}
sub check_for_dublicate_anr {
my #anrname = #_;
my %uniqueAnr = ();
foreach my $item (#anrname) {
unless ( $uniqueAnr{$item} ) {
# if we get here, we have not seen it before
$uniqueAnr{$item} = 1;
return 1;
}
}
}
You can simplify things with Path::Class and Path::Class::Rule:
use 5.010;
use warnings;
use Path::Class;
use Path::Class::Rule;
my $root = ".";
my #dirs = grep { -d $_ } dir($root)->children();
my $iter = Path::Class::Rule->new->file->name(qr{traces[_d]*})->iter(#dirs);
my $seen;
while ( my $file = $iter->() ) {
for ( $file->slurp( chomp => 1 ) ) {
next unless /Cmd line:\s+(com\.android\.\S*)/;
do_things( $file, $1 ) unless $seen->{$1}++;
}
}
sub do_things {
my ( $file, $str ) = #_;
say "new $str in the $file";
}

How to rename directories recursively

I want to rename directories recursively using File::Find::Rule, eg. remove extra spaces in each found but as I understand the module doesn't do finddepth and renames only one. Is there a way to do that. Thanks.
use autodie;
use strict ;
use warnings;
use File::Find::Rule;
my $dir = 'D:/Test';
my #fd = File::Find::Rule->directory
->in( $dir );
for my $fd ( #fd ) {
my $new = $fd;
$new =~ s/\s\s+/ /g;
print "$new\n";
rename $fd, $new;
}
You want to process the deeper results first, so process the list in reverse. You can only rename the leaf part of the path; you'll get to the more shallow parts later.
use Path::Class qw( dir );
for ( reverse #fd ) {
my $dir = dir($_);
my $parent = $dir->parent;
my $old_leaf = my $new_leaf = $dir->dir_list(-1);
$new_leaf =~ s/\s+/ /g;
if ($new_leaf ne $old_leaf) {
my $old_file = $parent->dir($old_leaf);
my $new_file = $parent->dir($new_leaf);
# Prevent accidental deletion of files.
if (-e $new_file) {
warn("$new_file already exists\n");
next;
}
rename($old_file, $new_file);
}
}
Answer to original question:
I don't see how FFR comes into play.
rename 'Test1/Test2/Test3', 'Test1/Test2/Dir3';
rename 'Test1/Test2', 'Test1/Dir2';
rename 'Test1', 'Dir1';
For arbitrary paths,
use Path::Class qw( dir );
my #parts1 = dir('Test1/Test2/Test3')->dir_list();
my #parts2 = dir('Dir1/Dir2/Dir3' )->dir_list();
die if #parts1 != #parts2;
for (reverse 0..$#parts1) {
my $path1 = dir(#parts1[ 0..$_ ]);
my $path2 = dir(#parts2[ 0..$_ ]);
rename($path1, $path2);
}
Or maybe you want to rename all Test1 to Dir1, Test2 to Dir2, and Test3 to Dir3, process the list in reverse order.
my %map = (
'Test1' => 'Dir1',
'Test2' => 'Dir2',
'Test3' => 'Dir3',
);
my $pat = join '|', map quotemeta, keys %map;
for ( reverse #fd ) {
my $o = $_;
my $n = $_;
$n =~ s{/\K($pat)\z}{$map{$1}};
if ($n ne $o) {
if (-e $n) {
warn("$n already exists\n");
next;
}
rename($o, $n);
}
}
I have a module for doing actions recursively in a directory tree. It didn't have the ability to act on the directories themselves though, so it took a little updating. I have uploaded version 0.03 of my File::chdir::WalkDir, but until it shows up, it can be installed from its GitHub repo, and now available using your fav CPAN utility. This script would then remove spaces from directory names inside the base directory 'Test' relative to the working directory:
#!/usr/bin/env perl
use strict;
use warnings;
use File::chdir::WalkDir 0.030;
use File::Copy;
my $job = sub {
my ($name, $in_dir) = #_;
#ONLY act on directories
return 0 unless (-d $name);
my $new_name = $name;
if ($new_name =~ s/\s+/ /g) {
move($name, $new_name);
}
};
walkdir( 'Test', $job, {'act_on_directories' => 1} );

Perl Get Parent Folder Name

What is the solution to get the name of the parent directory using File::Find. I know how to get only the filename or only the directory path but I don't know how to do this for the last containing directory.
For example, if the directory is /dir_1/dir_2/dir_3/.../dir_n/*.txt I need to get the 'dir_n' name.
use strict;
use warnings;
use File::Find;
my $dir = "some_path";
find(\&file_handle, $dir);
sub file_handle {
/\.txt$/ or return;
my $fd = $File::Find::dir;
my $fn = $File::Find::name;
# ...
}
Given the directory path, you then apply File::Basename (another core module) to the path to obtain the last portion of the directory.
use strict;
use warnings;
use File::Find;
use File::Basename;
my $dir = "some_path";
find(\&file_handle, $dir);
sub file_handle {
/\.txt$/ or return;
my $fd = $File::Find::dir;
my $fn = $File::Find::name;
my $dir = basename($fd);
# ....
}
#!/usr/local/bin/perl -w
use strict;
use File::Basename;
use Cwd 'abs_path';
my $f = "../some/path/to/this_directory/and_filename";
my $d = basename(dirname(abs_path($f)));
say $d;
returns "this_directory"
You can just split and grab the second-to-last element in the array:
my $fname = "/folder/sub-folder/filename.bin";
my #parts = split('/', $fname);
if( #parts > 1 ) {
return $parts[#parts - 2];
} else {
return '/';
}
If you are willing to install non-core modules, the Path::Class can come handy:
use Path::Class;
dir("some_dir")->recurse(callback => sub {
my $file = shift;
return if $file->is_dir;
return if $file =~ /\.txt$/i;
my $fn = $file->basename;
my $fd = $file->parent;
my $dir = $file->parent->parent;
});
It gives you handy objects instead of strings and imo nice operations on them.