Rename and number files in a directory - perl

I'm trying to rename all tif files in a folder and number them from 1 to x. Ex. initial filenames "image-2.tif" and "image-3.tif" would be rename "file1.tif" and "file2.tif".
Here is my code:
my $dirname = "../folder";
opendir (DIR, $dirname) or die "cannot open directory $dirname";
my #files = grep /.tif/, readdir DIR;
closedir (DIR);
my $basename = "file";
my $count = 1;
my $new;
foreach (#files) {
$new = "${basename}${count}.tif";
print "rename $_ ${basename}${count}.tif\n";
rename $_, $new;
$count++;
}
Although all files are read correctly, they are just not renamed.

You need to use a good path for your rename() function when you get the files from a different one. The module File::Spec can help to get it:
use File::Spec;
my $dirname = "../folder";
my $abs_path = File::Spec->rel2abs($dirname);
And:
foreach (#files) {
$new = "${basename}${count}.tif";
print "rename $_ ${basename}${count}.tif\n";
rename File::Spec->catfile($abs_path, $_), File::Spec->catfile($abs_path, $new);
$count++;
}

I would suggest changing your line:
rename $_, $new;
...to something more like:
rename($_, $new) or warn "rename: $_: $new: $!\n";
...and you should be able to see why it isn't working -- since rename returns false upon failure (and $! will tell you why it failed).
Also, the target of your rename operation (in your case: $new) needs to include the directory component as well (otherwise, your attempting to move the files into the process's current working directory (probably not what you intended)).
Finally, I would suggest that, instead of hard-coding the value of $dirname to a relative path, you should accept it as a command line argument. This allows your script to be run from any $PWD. See #ARGV in the perlvar manpage and/or the builtin shift command.
Therefore, something like this:
my $dirname = shift; # Accept dirname as commandline argument
opendir (DIR, $dirname) or die "cannot open directory $dirname";
my #files = grep /\.tif$/, readdir DIR; # Escape regex meta-char (.) and anchor to end of string ($).
closedir (DIR);
my $basename = "file";
my $count = 1;
my $new;
foreach (#files) {
$new = "${dirname}/${basename}${count}.tif"; # Include dirname in target path
print "rename $_ $new\n";
rename($_, $new) or warn "rename: $_: $new: $!\n"; #warn when rename fails
$count++;
}

I finally found exactly what I was looking for. With the help of Tim Peoples's response. The simplest response would be:
my $dirname = "../folder";
opendir (DIR, $dirname) or die "cannot open directory $dirname";
my #files = grep /[.]tif\z/, readdir DIR; #corrected pattern based on Sinan Ünür's comment
closedir (DIR);
my $basename = "file";
my $count = 1;
my $new;
foreach (#files) {
$new = "${basename}${count}.tif";
print "rename $_ $new\n";
rename ("$dirname/$_", "$dirname/$new") or warn "rename: $_: $new: $!\n"; #directory name added to both the original file name and new file name. Thanks to Tim for helping me found this error using warn.
$count++;
}

Related

Renaming files with perl

I'm very new to perl, and I want to rename a group of files so that they are lowercase instead of uppercase (so from SBC005.wav -> sbc005.wav).
use strict;
use warnings;
my $dirnam = "/Users/.../SoundFiles";
opendir(DIR, $dirnam) or die "Cannot open directory: $!";
my #files = readdir(DIR);
foreach my $oldfile (#files) {
my $newfile = lc($oldfile);
#print $newfile;
#print $oldfile;
rename $oldfile, $newfile or die "Cannot rename file: $!";
}
closedir(DIR);
I checked that the variables are working right with the commented out print statements, but when I run the program I get a message that says "Cannot rename file: Invalid argument at rename.pl line 13." I'm not sure what I'm doing wrong here.
Thank you so much!
edit:
Thank you so much to the answer below! I found that this code using glob works too, but the code below works better because it does not have to be in the same directory as the sound files (as the glob code does)
use strict;
use warnings;
my #files = glob("*.wav");
foreach my $oldfile (#files) {
my $newfile = lc($oldfile);
#print $newfile;
#print $oldfile;
rename $oldfile, $newfile or die "Cannot rename file: $!";
}
exit 0;
Try this. you forgot to add the path of directory while renaming.
use strict;
use warnings;
my $dirnam = "/Users/.../SoundFiles";
opendir(DIR, $dirnam) or die "Cannot open directory: $!";
my #files = readdir(DIR);
foreach my $oldfile (#files)
{
unless($oldfile eq "." || $oldfile eq ".." )
{
my $newfile = lc($oldfile);
rename "$dirnam/$oldfile", "$dirnam/$newfile" or die "Cannot rename file: $!";
}
}

Odd file handling in perl on OS X

I'm very much a perl newbie, so bear with me.
I was looking for a way to recurse through folders in OS X and came across this solution: How to traverse all the files in a directory...
I modified perreal's answer (see code below) slightly so that I could specify the search folder in an argument; i.e. I changed my #dirs = ("."); to my #dirs = ($ARGV[0]);
But for some reason this wouldn't work -- it would open the folder, but would not identify any of the subdirectories as folders, apart from '.' and '..', so it never actually went beyond the specified root.
If I actively specified the folder (e.g. \Volumes\foo\bar) it still doesn't work. But, if I go back to my #dirs = ("."); and then sit in my desired folder (foo\bar) and call the script from its own folder (foo\boo\script.pl) it works fine.
Is this 'expected' behaviour? What am I missing?!
Many thanks,
Mat
use warnings;
use strict;
my #dirs = (".");
my %seen;
while (my $pwd = shift #dirs) {
opendir(DIR,"$pwd") or die "Cannot open $pwd\n";
my #files = readdir(DIR);
closedir(DIR);
foreach my $file (#files) {
if (-d $file and ($file !~ /^\.\.?$/) and !$seen{$file}) {
$seen{$file} = 1;
push #dirs, "$pwd/$file";
}
next if ($file !~ /\.txt$/i);
my $mtime = (stat("$pwd/$file"))[9];
print "$pwd $file $mtime";
print "\n";
}
}
The problem is that you are using the -d operator on the file basename without its path. Perl will look in the current working directory for a directory of that name and return true if it finds one there, when it should be looking in $pwd.
This solution changes $file to always hold the full name of the file or directory, including the path.
use strict;
use warnings;
my #dirs = (shift);
my %seen;
while (my $pwd = shift #dirs) {
opendir DIR, $pwd or die "Cannot open $pwd\n";
my #files = readdir DIR;
closedir DIR;
foreach (#files) {
next if /^\.\.?$/;
my $file = "$pwd/$_";
next if $seen{$file};
if ( -d $file ) {
$seen{$file} = 1;
push #dirs, $file;
}
elsif ( $file =~ /\.txt$/i ) {
my $mtime = (stat $file)[9];
print "$file $mtime\n";
}
}
}
use full path with -d
-d "$pwd/$file"

perl iterate through directories

I'm trying to get the name of all directories in the specified path
I tried the following but that gives me every level down not just at the path i specified
find(\&dir_names, "C:\\mydata\\");
sub dir_names {
print "$File::Find::dir\n" if(-f $File::Find::dir,'/');
}
my #dirs = grep { -d } glob 'C:\mydata\*';
Use opendir instead
opendir DIR, $dirname or die "Couldn't open dir '$dirname': $!";
my #files = readdir(DIR);
closedir DIR;
#next processing...
EDIT:
"This will give all the files, not just the directories. You'd still have to grep."
Yes, and in that case you can just use file test operator to see whether it's a directory or not.
In Windows:
$dirname="C:\\";
opendir(DIR, $dirname);
#files = readdir(DIR);
closedir DIR;
foreach $key (#files)
{
if(-d "$dirname\\$key")
{
print "$key\n";
}
}
See chapter 2 Filesystems from Automating System Administration with Perl. That provides us with this:
sub ScanDirectory{
my ($workdir) = shift;
chdir($workdir) or die "Unable to enter dir $workdir:$!\n";
opendir(DIR, ".") or die "Unable to open $workdir:$!\n";
my #names = readdir(DIR) or die "Unable to read $workdir:$!\n";
closedir(DIR);
foreach my $name (#names){
next if ($name eq ".");
next if ($name eq "..");
if (-d $name){ # is this a directory?
#Whatever you want to do goes here.
}
}
}
glob or readdir would probably be my choice too. Another way to do it is to use the windows dir command to do the job:
my #dirs = qx(dir /AD /B);
chomp #dirs;

Perl program help on opendir and readdir

So I have a program that I want to clean some text files. The program asks for the user to enter the full pathway of a directory containing these text files. From there I want to read the files in the directory, print them to a new file (that is specified by the user), and then clean them in the way I need. I have already written the script to clean the text files.
I ask the user for the directory to use:
chomp ($user_supplied_directory = <STDIN>);
opendir (DIR, $user_supplied_directory);
Then I need to read the directory.
my #dir = readdir DIR;
foreach (#dir) {
Now I am lost.
Any help please?
I'm not certain of what do you want. So, I made some assumptions:
When you say clean the text file, you meant delete the text file
The names of the files you want to write into are formed by a pattern.
So, if I'm right, try something like this:
chomp ($user_supplied_directory = <STDIN>);
opendir (DIR, $user_supplied_directory);
my #dir = readdir DIR;
foreach (#dir) {
next if (($_ eq '.') || ($_ eq '..'));
# Reads the content of the original file
open FILE, $_;
$contents = <FILE>;
close FILE;
# Here you supply the new filename
$new_filename = $_ . ".new";
# Writes the content to the new file
open FILE, '>'.$new_filename;
print FILE $content;
close FILE;
# Deletes the old file
unlink $_;
}
I would suggest that you switch to File::Find. It can be a bit of a challenge in the beginning but it is powerful and cross-platform.
But, to answer your question, try something like:
my #files = readdir DIR;
foreach $file (#files) {
foo($user_supplied_directory/$file);
}
where "foo" is whatever you need to do to the files. A few notes might help:
using "#dir" as the array of files was a bit misleading
the folder name needs to be prepended to the file name to get the right file
it might be convenient to use grep to throw out unwanted files and subfolders, especially ".."
I wrote something today that used readdir. Maybe you can learn something from it. This is just a part of a (somewhat) larger program:
our #Perls = ();
{
my $perl_rx = qr { ^ perl [\d.] + $ }x;
for my $dir (split(/:/, $ENV{PATH})) {
### scanning: $dir
my $relative = ($dir =~ m{^/});
my $dirpath = $relative ? $dir : "$cwd/$dir";
unless (chdir($dirpath)) {
warn "can't cd to $dirpath: $!\n";
next;
}
opendir(my $dot, ".") || next;
while ($_ = readdir($dot)) {
next unless /$perl_rx/o;
### considering: $_
next unless -f;
next unless -x _;
### saving: $_
push #Perls, "$dir/$_";
}
}
}
{
my $two_dots = qr{ [.] .* [.] }x;
if (grep /$two_dots/, #Perls) {
#Perls = grep /$two_dots/, #Perls;
}
}
{
my (%seen, $dev, $ino);
#Perls = grep {
($dev, $ino) = stat $_;
! $seen{$dev, $ino}++;
} #Perls;
}
The crux is push(#Perls, "$dir/$_"): filenames read by readdir are basenames only; they are not full pathnames.
You can do the following, which allows the user to supply their own directory or, if no directory is specified by the user, it defaults to a designated location.
The example shows the use of opendir, readdir, stores all files in the directory in the #files array, and only files that end with '.txt' in the #keys array. The while loop ensures that the full path to the files are stored in the arrays.
This assumes that your "text files" end with the ".txt" suffix. I hope that helps, as I'm not quite sure what's meant by "cleaning the files".
use feature ':5.24';
use File::Copy;
my $dir = shift || "/some/default/directory";
opendir(my $dh, $dir) || die "Can't open $dir: $!";
while ( readdir $dh ) {
push( #files, "$dir/$_");
}
# store ".txt" files in new array
foreach $file ( #files ) {
push( #keys, $file ) if $file =~ /(\S+\.txt\z)/g;
}
# Move files to new location, even if it's across different devices
for ( #keys ) {
move $_, "/some/other/directory/"; || die "Couldn't move files: $!\n";
}
See the perldoc of File::Copy for more info.

How can I list all files in a directory using Perl?

I usually use something like
my $dir="/path/to/dir";
opendir(DIR, $dir) or die "can't open $dir: $!";
my #files = readdir DIR;
closedir DIR;
or sometimes I use glob, but anyway, I always need to add a line or two to filter out . and .. which is quite annoying.
How do you usually go about this common task?
my #files = grep {!/^\./} readdir DIR;
This will exclude all the dotfiles as well, but that's usually What You Want.
I often use File::Slurp. Benefits include: (1) Dies automatically if the directory does not exist. (2) Excludes . and .. by default. It's behavior is like readdir in that it does not return the full paths.
use File::Slurp qw(read_dir);
my $dir = '/path/to/dir';
my #contents = read_dir($dir);
Another useful module is File::Util, which provides many options when reading a directory. For example:
use File::Util;
my $dir = '/path/to/dir';
my $fu = File::Util->new;
my #contents = $fu->list_dir( $dir, '--with-paths', '--no-fsdots' );
I will normally use the glob method:
for my $file (glob "$dir/*") {
#do stuff with $file
}
This works fine unless the directory has lots of files in it. In those cases you have to switch back to readdir in a while loop (putting readdir in list context is just as bad as the glob):
open my $dh, $dir
or die "could not open $dir: $!";
while (my $file = readdir $dh) {
next if $file =~ /^[.]/;
#do stuff with $file
}
Often though, if I am reading a bunch of files in a directory, I want to read them in a recursive manner. In those cases I use File::Find:
use File::Find;
find sub {
return if /^[.]/;
#do stuff with $_ or $File::Find::name
}, $dir;
If some of the dotfiles are important,
my #files = grep !/^\.\.?$/, readdir DIR;
will only exclude . and ..
When I just want the files (as opposed to directories), I use grep with a -f test:
my #files = grep { -f } readdir $dir;
Thanks Chris and Ether for your recommendations. I used the following to read a listing of all files (excluded directories), from a directory handle referencing a directory other than my current directory, into an array. The array was always missing one file when not using the absolute path in the grep statement
use File::Slurp;
print "\nWhich folder do you want to replace text? " ;
chomp (my $input = <>);
if ($input eq "") {
print "\nNo folder entered exiting program!!!\n";
exit 0;
}
opendir(my $dh, $input) or die "\nUnable to access directory $input!!!\n";
my #dir = grep { -f "$input\\$_" } readdir $dh;