Error while trying to rename files in script and cmd line - perl

$dir = "/home/naveen/mp3tag/testfolder";
opendir(DMP3, $dir) || die("Cannot open directory");
#files= readdir(DMP3;
foreach $f (#files)
{
unless ( ($f eq ".") || ($f eq "..") )
{
$oldfile = $f;
$newfile = $f;
$newfile =~ s/ /_/g;
print "Old file: $oldfile \t";
print "New file: $newfile";
print "\n";
rename ("$oldfile", "$newfile") or warn "Couldn't rename $oldfile to $newfile !\n";
}
}
I'm writing a simple program to add underscores to an existing file and rename it. This is how far ive gotten with the code. However its not able to rename the file and gives me a warning and i'm not sure where the mistake is.
Also when i tried the same line on the cmd line I get the following error msg.
$ rename Jacques\ Greene\ -\ Clark\ \(Original\ Mix\).mp3 JG - C.mp3
Bareword "mp3" not allowed while "strict subs" in use at (eval 1) line 1.
$ rename Jacques\ Greene\ -\ Clark\ \(Original\ Mix\) JG - C
Can't locate object method "Original" via package "Mix" (perhaps you forgot to load "Mix"?) at (eval 1) line 1.

You're trying to rename all the files in the directory, not just one file. The error could be a great many things, since you did not mention it, I could only guess.
rename is, as I recall, a bit wonky, and using move from File::Copy is a safer bet. Also, you might want to avoid renaming directories. Using a more intuitive interface would probably not be a bad idea either.
One of your biggest mistakes is not using use strict; use warnings;. The amount of trouble you bring on yourself by leaving these out cannot be underestimated.
use strict;
use warnings;
use File::Copy qw(move);
for (#ARGV) {
my $org = $_;
tr/ /_/;
move($org, $_) or warn "Couldn't move $org to $_: $!";
}
Usage:
perl script.pl /home/naveen/mp3tag/testfolder/*.mp3
So, as long as you give a proper glob as argument, your script will only affect those files. You can add more checks to make it stricter.
If that commandline attempt of yours is meant to be with using the tool from /usr/bin/rename, I would hazard a guess that your error can simply be avoided by using quotes.

This working example might help
use strict;
use warnings;
use File::Copy;
my $dir = '/home/naveen/mp3tag/testfolder';
my #mp3s = glob ("$dir/*.mp3");
for my $mp3 (#mp3s) {
my $new_mp3 = $mp3;
$new_mp3 =~ s/\s/_/g;
move($mp3, $new_mp3);
}

You are calling rename in /usr/bin. If you want to call your program, choose a better name for it, or call it with full path specified.
But before you do, add at least the missing right bracket to readdir.

Related

Perl readdir warning - Name "main::DIR" used only once: possible typo

I have a script which lists the possible file in particular directory. The code works fine, but how to avoid this warning?
#!/usr/bin/perl
use strict;
use warnings;
use autodie;
my $logpath = "C:/Users/Vinod/Perl/Log";
opendir(DIR, $logpath);
while (my $file = readdir(DIR)) {
next unless (-f "$logpath/$file");
print "FILENAME:$file\n";
}
closedir(DIR);
Warning shows while compiling or running the script is:
$ perl -cw log_fetch.pl
Name "main::DIR" used only once: possible typo at log_fetch.pl line ...
log_fetch.pl syntax OK
This appears to be a weird side-effect of using use autodie;.
The warning can be silenced as follows:
sub x { *DIR } # Silence spurious "used only once" warning.
However, you are needlessly using a global variable (*DIR). It would be far better to use a lexical variable, and this would moot the problem.
opendir(my $DIR, $logpath);

Rename all .txt files in a directory and then open that file in perl

I need some help with file manipulations and need some expert advice.
It looks like I am making a silly mistake somewhere but I can't catch it.
I have a directory that contains files with a .txt suffix, for example file1.txt, file2.txt, file3.txt.
I want to add a revision string, say rev0, to each of those files and then open the modified files. For instance rev0_file1.txt, rev0_file2.txt, rev0_file3.txt.
I can append rev0, but my program fails to open the files.
Here is the relevant portion of my code
my $dir = "path to my directory";
my #::tmp = ();
opendir(my $DIR, "$dir") or die "Can't open directory, $!";
#::list = readdir($DIR);
#::list2 = sort grep(/^.*\.txt$/, #::list);
foreach (#::list2) {
my $new_file = "$::REV0" . "_" . "$_";
print "new file is $new_file\n";
push(#::tmp, "$new_file\n");
}
closedir($DIR);
foreach my $cur_file (<#::tmp>) {
$cur_file = $_;
print "Current file name is $cur_file\n"; # This debug print shows nothing
open($fh, '<', "$cur_file") or die "Can't open the file\n"; # Fails to open file;
}
Your problem is here:
foreach my $cur_file(<#::tmp>) {
$cur_file = $_;
You are using the loop variable $cur_file, but you overwrite it with $_, which is not used at all in this loop. To fix this, just remove the second line.
Your biggest issue is the fact you are using $cur_file in your loop for the file name, but then reassign it with $_ even though $_ won't have a value at that point. Also, as Borodin pointed out, $::REV0 was never defined.
You can use the move command from the File::Copy to move the files, and you can use File::Find to find the files you want to move:
use strict;
use warnings;
use feature qw(say);
use autodie;
use File::Copy; # Provides the move command
use File::Find; # Gives you a way to find the files you want
use constant {
DIRECTORY => '/path/to/directory',
PREFIX => 'rev0_',
};
my #files_to_rename;
find (
sub {
next unless /\.txt$/; # Only interested in ".txt" files
push #files_to_rename, $File::Find::name;
}, DIRECTORY );
for my $file ( #files_to_rename ) {
my $new_name = PREFIX . $file;
move $file, $new_name;
$file = $new_name; # Updates #files_to_rename with new name
open my $file_fh, "<", $new_name; # Open the file here?
...
close $file_fh;
}
for my $file ( #files_to_rename ) {
open my $file_fh, "<", $new_name; # Or, open the file here?
...
close $file_fh;
}
See how using Perl modules can make your task much easier? Perl comes with hundreds of pre-installed packages to handle zip files, tarballs, time, email, etc. You can find a list at the Perldoc page (make sure you select the version of Perl you're using!).
The $file = $new_name is actually changing the value of the file name right inside the #files_to_rename array. It's a little Perl trick. This way, your array refers to the file even through it has been renamed.
You have two choices where to open the file for reading: You can rename all of your files first, and then loop through once again to open each one, or you can open them after you rename them. I've shone both places.
Don't use $:: at all. This is very bad form since it overrides use strict; -- that is if you're using use strict to begin with. The standard is not to use package variables (aka global variables) unless you have to. Instead, you should use lexically scoped variables (aka local variables) defined with my.
One of the advantages of the my variable, I really don't need the close command since the variable falls out of scope with each iteration of the loop and disappears entirely once the loop is complete. When the variable that contains the file handle falls out of scope, the file handle is automatically closed.
Always include use strict;, use warnings at the top of EVERY script. And use autodie; anytime you're doing file or directory processing.
There is no reason why you should be prefixing your variables with :: so please simplify your code like the following:
use strict;
use warnings;
use autodie;
use File::Copy;
my $dir = "path to my directory";
chdir($dir); # Make easier by removing the need to prefix path information
foreach my $file (glob('*.txt')) {
my $newfile = 'rev0_'.$file;
copy($file, $newfile) or die "Can't copy $file -> $newfile: $!";
open my $fh, '<', $newfile;
# File processing
}
What you've attempted to store is the updated name of the file in #::tmp. The file hasn't been renamed, so it's little surprise that the code died because it couldn't find the renamed file.
Since it's just renaming, consider the following code:
use strict;
use warnings;
use File::Copy 'move';
for my $file ( glob( "file*.txt" ) ) {
move( $file, "rev0_$file" )
or die "Unable to rename '$file': $!";
}
From a command line/terminal, consider the rename utility if it is available:
$ rename file rev0_file file*.txt

Recursive directory traversal in Perl

I'm trying to write a script that prints out the file structure starting at the folder the script is located in. The script works fine without the recursive call but with that call it prints the contents of the first folder and crashes with the following message: closedir() attempted on invalid dirhandle DIR at printFiles.pl line 24. The folders are printed and the execution reaches the last line but why isn't the recursive call done? And how should I solve this instead?
#!/usr/bin/perl -w
printDir(".");
sub printDir{
opendir(DIR, $_[0]);
local(#files);
local(#dirs);
(#files) = readdir(DIR);
foreach $file (#files) {
if (-f $file) {
print $file . "\n";
}
if (-d $file && $file ne "." && $file ne "..") {
push(#dirs, $file);
}
}
foreach $dir (#dirs) {
print "\n";
print $dir . "\n";
printDir($dir);
}
closedir(DIR);
}
You should always use strict; and use warnings; at the start of your Perl program, especially before you ask for help with it. That way Perl will show up a lot of straightforward errors that you may not notice otherwise.
The invalid filehandle error is likely because DIR is a global directory handle and has been closed already by a previous execution of the subroutine. It is best to always used lexical handles for both files and directories, and to test the return code to make sure the open succeeded, like this
opendir my $dh, $_[0] or die "Failed to open $_[0]: $!";
One advantage of lexical file handles is that they are closed implicitly when they go out of scope, so there is no need for your closedir call at the end of the subroutine.
local isn't meant to be used like that. It doesn't suffice as a declaration, and you are creating a temporary copy of a global variable that everything can access. Best to use my instead, like this
my #dirs;
my #files = readdir $dh;
Also, the file names you are using from readdir have no path, and so your file tests will fail unless you either chdir to the directory being processed or append the directory path string to the file name before testing it.
Use the File::Find module. The way i usually do this is using the find2perl tool which comes with perl, which takes the same parameters as find and creates a suitable perl script using File::Find. Then i fine-tune the generated script to do what i want it to do. But it's also possible to use File::Find directly.
Why not use File::Find?
use strict; #ALWAYS!
use warnings; #ALWAYS!
use File::Find;
find(sub{print "$_\n";},".");

How to read multiple files from a directory, extract specific strings and ouput to an html file?

Greetings,
I have the following code and am stuck on how I would proceed to modify it so it will ask for the directory, read all files in the directory, then extract specific strings and ouput to an html file? Thanks in advance.
#!/usr/local/bin/perl
use warnings;
use strict;
use Cwd;
print "Enter filename: "; # Should be Enter directory
my $perlfile =STDIN;
open INPUT_FILE, $perlfile || die "Could not open file: $!";
open OUTPUT, '>out.html' || die "Could not open file: $!";
# Evaluates the file and imports it into an array.
my #comment_array = ;
close(INPUT_FILE);
chomp #comment_array;
#comment_array = grep /^\s*#/g, #comment_array;
my $comment;
foreach $comment (#comment_array) {
$comment =~ /####/; #Pattern match to grab only #s
# Prints comments to screen
Print results in html format
# Writes comments to output.html
Writes results to html file
}
close (OUTPUT);
Take it one step at a time. You have a lot planned, but so far you haven't even changed your prompt string to ask for a directory.
To read the entered directory name, your:
my $perlfile =STDIN;
gives an error (under use strict;). Start by looking that error up (use diagnostics; automates this) and trying to figure out what you should be doing instead.
Once you can prompt for a directory name and print it out, then add code to open the directory and read the directory. Directories can be opened and read with opendir and readdir. Make sure you can read the directory and print out the filenames before going on to the next step.
a good starting point to learn about specific functions (from the cmd line)
perldoc -f opendir
However, your particular problem is answered as follows, you can also use command line programs and pipe them into a string to simplify file handling ('cat') and pattern matching ('grep').
#!/usr/bin/perl -w
use strict;
my $dir = "/tmp";
my $dh;
my #patterns;
my $file;
opendir($dh,$dir);
while ($file = readdir($dh)){
if (-f "$dir/$file"){
my $string = `cat $dir/$file | grep pattern123`;
push #patterns, $string;
}
}
closedir($dh);
my $html = join("<br>",#patterns);
open F, ">out.html";
print F $html;
close F;

Beginner question - Perl script can't find file

I am new to Perl and have created a simple Perl program. However, it never seems to find a file on the file system. The code is:
my $filename = 'test.txt';
if (-e $filename)
{
print "exists";
}
else
{
print "not found";
}
I have also tried to use the exact path of the file "test.txt" but it still does not work; it never finds the file. Does anybody know what I'm doing wrong?
Your code seems correct, which either means that "test.txt" really doesn't exist (or if there is, it's not in the working directory).
For example, if you have this:
/home/you/code/test.pl
/home/you/test.txt
And run your code like this:
$ cd code
$ perl test.pl
Then your test file won't be found.
It may help to make your script print the current working directory before it does anything:
use Cwd;
print getcwd();
...
Write the full path to your file. It should work. For example:
folder/files/file.txt
and probably use " instead of '
Here are some possibilities for what might be wrong:
Regarding the full path: You are using windows and just copied the full path into your string. In this case don't forget to escape the backspaces in your path. For example: C:\myFolder\test.txt must be put into the variable like this: my $filename = "C:\\myFolder\\test.txt"
Your script uses another directory than the one your file is in. Here's how you can find out where your script is executed and where it looks for the relative file path test.txt:
use strict;
use Cwd;
print getcwd;
If you are in the wrong filepath you have to switch to the right one before you execute your script. Use the shell command cd for this.
You are in the right directory and/or are using the right full path but the file has another name. You can use perl to find out what the actual name is. Change into the directory where the file is before you execute this script:
use strict;
opendir my $dirh, '.';
print "'", join ("'\n'", grep $_ ne '.' && $_ ne '..', readdir $dirh), "'\n";
closedir $dirh;
This prints all files in the current directory in single quotes. Copy the filename from your file and use it in the code.
Good luck! :)
Use this script:
my $filename=glob('*.txt');
print $filename;
if (-e $filename)
{
print "exists";
}
else
{
print "not found";
}