I am a relatively infrequent user of Perl. I have written a script that takes two input files with the same name but different extensions, processes them and outputs a third file. It works when I specify the name of the file, but I want it to search for all relevant files in a directory and process all of them. However, when I do this it keeps saying there is no such file or directory - even though I'm sure there is. I've looked at all the relevant pages on this site and tried the suggestions there, but it still doesn't work. I'm stumped.
Here is the code, missing the processing of the files themselves, as that is long and not relevant.
use strict;
use warnings;
use autodie;
#specify single file - works when this is not commented and the loops below are
#my $file = "BE_Read01_f2-2";
#on a Mac
my $dir = "/Users/sashacalhoun/Documents/supervision/tariq/Syllables";
opendir(my $dh, $dir);
while (my $file = readdir($dh)) {
if($file=~s/(.+-CV)\.TextGrid/$1/) {
print "$file\n";
open(my $syl, "<", "$dir/${file}.par");
while(my $line=<$syl>) {
#processes this file - not included
}
close($syl);
my $gridfile = "$file-CV.TextGrid";
my $outfile = "$file-syl.TextGrid";
open(my $grid, "<", $gridfile);
open(my $out, ">", $outfile);
while(my $line=<$grid>) {
print $out $line;
# other processing of this file
}
close($grid);
close($out);
}
}
It says: Can't open '/Users/sashacalhoun/Documents/supervision/tariq/Syllables/BE_Read01_f2-1-CV.par' for reading: 'No such file or directory' at ./get_syl.pl line 36
Thanks very much for your help.
From your error message, it is apparent that the line causing the error is this line:
open(my $syl, "<", "$dir/${file}.par");
it keeps saying there is no such file or directory - even though I'm sure there is.
You can prove to yourself that no such file exists by copying the path in the error message:
Can't open
'/Users/sashacalhoun/Documents/supervision/tariq/Syllables/BE_Read01_f2-1-CV.par'
for reading: 'No such file or directory' at ./get_syl.pl line 36
and doing this:
$ ls /Users/sashacalhoun/Documents/supervision/tariq/Syllables/BE_Read01_f2-1-CV.par
And, instead of constructing paths yourself, you should consider using File::Spec, etc.:
use File::Spec::Functions;
my $path = catfile $dirname, $fname;
And, you should probably check whether the filename is a file or directory before constructing your path:
if (-f $path -r $path) { #then it's a file that is readable...
#Here you might want to skip files whose names start with a '.', i.e. hidden files
}
And, your debugging print statement should be this:
my $path = "$dir/$file.par";
print "$path\n";
open(my $syl, "<", "$path.par");
By the way, if you don't like typing \n after every print statement, you can use say():
say "After printing this text, perl automatically adds a '\n' to the end of the output";
You do have to employ a use statement for say() to work, e.g.
use 5.010;
And, you should add a die() clause to your opendir()/open() statements:
opendir(my $DIR, $dir)
or die "Couldn't open $dir: $!";
Related
I'm new to Perl and trying to put output files in a different directory.piece of code is as below
use File::Basename;
use File::Copy;
use File::Path;
foreach my $file (sort({uc($a) cmp uc($b)} keys(%$ch_ref))) {
my $num = keys(%{$$ch_ref{$file}});
print "\n -> $string $file ($num):\n";
foreach my $sid (keys(%{$$ch_ref{$file}})) {
if ($type == $PRINT_OLD) {
open ( my $output, '>>',$file );
print {$output} " something";
close ( $output ) or die $!;
}
The third argument to open() is the full path to the file that you want to open. Currently, you're just giving it the filename. but you can expand that to include the directory as well.
Something like this:
my $dir = '/path/to/some/directory';
open my $output, '>>', $dir . $string . '_' . $file;
You should really be checking the success of the open() call, and it's a bit easier to give a sensible error message if you build the filename into a variable first.
my $dir = '/path/to/some/directory';
my $filename = "$dir${string}_$file";
open my $output, '>>', $filename
or die "Can't open $filename: $!";
Note that using ${string} instead of $string means that you can use it directly in the string without the name getting tangled up with the following _ character.
I'd also strongly recommend dropping your use of prototypes on your subroutine. Perl prototypes are often far more trouble than they are worth.
Also, there's no need to open() and close() your file so many times. Just open it at the top of the loop (it will be automatically closed at the end as $output goes out of scope).
Below Codes is able to read only txt files and print out the filename and size into the filename.txt.
my $directory = 'c:\modules\SFTP';
my $file='filename.txt';
my $OUTFILE;
open $OUTFILE, '>>', $file;
my #files = do {
opendir my $dh, $directory;
grep {/^.*\.txt\z/si} readdir($dh);
};
foreach(#files){
my $filesize = stat($_)->size;
print { $OUTFILE } "$filesize $_" ,"\n";
}
My question is there anyway to read two specific file formats files instead of only one?
Eg. Folder A
textfile1.txt
textfile2.txt
testfile3.fcd
Expected Result:
Able to get and print out the filename and size for two specific formats (.txt and .fcd) files instead of only one (.txt)
What should I change or add in my code so I can get my expected result? Any related post or useful resources to share? Thanks!
You've got most of it right. Make sure you have parens around opendir. use warnings should have pointed that out. Just add another pattern to your grep. Also, open your output file after you have read the directory other wise you will get your output file listed in the output.
use strict;
use warnings;
use autodie;
use File::stat;
my $directory = 'c:\modules\SFTP';
my $file = 'filename.txt';
opendir (my $dh, $directory);
my #files = grep(/^.*\.(txt|fcd)\z/si, readdir $dh);
open my $OUTFILE, '>', $file;
foreach(#files){
my $filesize = stat($_)->size;
print $OUTFILE "$filesize $_" ,"\n";
}
As has already been stated, all you need to filter based off more than one extension is to setup an alteration in your regular expression:
/\.(?:txt|fcd)$/i
Note, that I've simplified your regex a little bit as well since it's not necessary to match against the beginning of the filename.
Enhancement by using Path::Class
I would like to recommend that you look into Path::Class for easier file and path manipulation.
The following does the same thing as your script, but ensures you aren't missing path information in your file test:
use strict;
use warnings;
use autodie;
use Path::Class;
my $dir = dir('c:\modules\SFTP');
open my $outfh, '>>', 'filename.txt';
while (my $file = $dir->next) {
next unless $file =~ /\.(?:txt|fcd)$/;
printf $outfh "%s %s\n", $file->stat->size, $file->basename;
}
My goal: list the *gz files in a directory with name and creation date.
I wrote the following
#!/usr/bin/perl
use strict;
use warnings;
use File::stat;
use Time::localtime;
my $directory = '/home/hans/.config/cqrlog/database';
opendir (DIR, $directory) or die $!;
my #files = (readdir(DIR));
closedir(DIR);
foreach $_ (#files) {
# Use a regular expression to find files ending with .gz
if ($_ =~ m/\.gz$/) {
my $file_name = $_;
my $file_time = (stat($_))[9];
print "$file_time\n";
}
}
But I do keep getting the often seen error "Use of uninitialized value $file_time in concatenation (.) or string at ./perl-matching-files.pl line 18." which is the print line.
I also tried the following:
foreach $_ (#files) {
# Use a regular expression to find files ending with .gz
if ($_ =~ m/\.gz$/) {
my $file_name = $_;
my #file_time_array = (stat($_));
my $file_time = $file_time_array[9];
print $file_name , " - " , $file_time , "\n";
}
}
But again it barfs at the last print line. I also tried a while-loop, but wit the same results. The file names are printed out, though, so I must be doing something right. I feel that when reading through the array the time stamp of the file is not read, but I am not that much of an expert to know what is going wrong. It seems to always come down to the print line. Any insight is appreciated. Cheers.
Instead of
my $file_time = (stat($_))[9];
try
my $file_time = (stat("$directory/$_"))[9];
otherwise you're looking for /home/hans/.config/cqrlog/database files in the current directory which could work ONLY if you're already in mentioned directory.
stat returns the empty list if stat fails. Therefore consider test the error code, especially when facing a problem like you were:
my $st = stat($_) or die "No $_: $!";
This would've returned:
No <filename.gz>: No such file or directory at ...
As mpapec already pointed out, this is because you aren't including the path information in the stat call. There are three possible solutions:
1) chdir to the directory your iterating over
chdir $directory;
2) Use a glob instead of readdir
#!/usr/bin/perl
use strict;
use warnings;
my $directory = '/home/hans/.config/cqrlog/database';
for my $file_name (glob("$directory/*.gz")) {
my $st = stat($file_name) or die "No $file_name: $!";
my $file_time = $st->[9];
print "$file_time\n";
}
3) Or manually add the path to the fqfn
my #file_time_array = stat("$directory/$_") or die "No $_: $!";
Thank you guys. After two days I got it figured out.
You were both right about the path not being specified enough. Fixed that.
Miller: the glob thing worked after I added use File::stat. I never worked with globs, so thanks for steering me in that direction. Learned a lot from it. Cheers.
In the end I tried the OOP interface for stat after fiddling for an hour with single file examples:
my $file_time = stat("$directory/$file_name")->mtime;
This got me what I wanted, so I tried the same method with the array element number:
my $file_time = (stat("$file_name"))->[9] or die "No $_: $!";
This also worked. So it all came down to adding "->"
This is my final code that works. I know it can be prettier/better/more efficient, but for now it is fine with me, because I wrote it myself. Time to get on with some additions because it is going to be a script only run on my own machine to handle some automation tasks.
#!/usr/bin/perl
use strict;
use warnings;
use File::stat;
use Time::localtime;
my $directory = '/home/hans/.config/cqrlog/database';
opendir (DIR, $directory) or die $!;
my #files = (readdir(DIR));
closedir(DIR);
foreach $_ (#files) {
# Use a regular expression to find files ending with .gz
if ($_ =~ m/\.gz$/) {
# my $file_time = stat("$directory/$_")->mtime;
my $file_time = (stat("$directory/$_"))->[9] or die "No $_: $!";
print "$_\n";
print "$file_time\n";
}
}
My code displays all files within the directory, But I need it not to display hidden files such as "." and "..".
opendir(D, "/var/spool/postfix/hold/") || die "Can't open directory: $!\n";
while (my $f = readdir(D))
{
print "MailID :$f\n";
}
closedir(D);
It sounds as though you might be wanting to use the glob function rather than readdir:
while (my $f = </var/spool/postfix/hold/*>) {
print "MailID: $f\n";
}
<...> is an alternate way of globbing, you can also just use the function directly:
while (my $f = glob "/var/spool/postfix/hold/*") {
This will automatically skip the hidden files.
Just skip the files you don't want to see:
while (my $f = readdir(D))
{
next if $f eq '.' or $f eq '..';
print "MailID :$f\n";
}
On a Linux system, "hidden" files and folders are those starting with a dot.
It is best to use lexical directory handles (and file handles).
It is also important to always use strict and use warnings at the start of every Perl program you write.
This short program uses a regular expression to check whether each name starts with a dot.
use strict;
use warnings;
opendir my $dh, '/var/spool/postfix/hold' or die "Can't open directory: $!\n";
while ( my $node = readdir($dh) ) {
next if $node =~ /^\./;
print "MailID: $node\n";
}
I have info contained in a variable that I need to have written to a file. My script needs to be create the file and then write to it.
Here's my current script:
my $file_location = '/network/$custom_directory/$custom_filename';
open(my $file ">", $file_location) or die $!;
print $file "$variable_data";
close $file;
I'm getting the feeling that my script is getting hung up on the actual file creation, rather than the variable-writing process.
The error I get when I run the script is: 'No such file or directory' at the line where I try to open the file.
You have a syntax error in your programme. All three arguments of open must be separated by commas.
open my $file, '>', $file_location or die $!;
Single quotes do not interpolate, unlike double quotes, so you probably need them in the file path:
my $file_location = "/network/$custom_directory/$custom_filename";
BTW: Including a sole variable into double quotes server no purpose for string contents. You can equivalently
print $file $variable_data;
You didn’t say what your error is.
But you’re missing a comma.
You also have the wrong quotes.
You also (probably) forgot the newline at the end.
And you forgot to check that the close succeeded lest your filesystem should have filled up.
You may have forgotten the binmode or the encoding.
Which gives you something like this, with obligatory preamble:
#!/usr/bin/perl
use strict;
use warnings;
my $custom_directory = "something old";
my $custom_filename = "something new";
my $data = "something borrowed";
my $path = "/network/$custom_directory/$custom_filename";
open(my $handle, ">", $path) || die "can't open $path: $!";
binmode($handle); # for raw; else set the encoding
print $handle "$data\n";
close($handle) || die "can't close $path: $!";
Two things: First the file location is in single-quotes, so the $ variables won't be interpolated. Second, you're missing a comma in the call to open. The code should read:
my $file_location = "/network/$custom_directory/$custom_filename";
open(my $file, ">", $file_location) or die $!;
First,
use strict;
use warnings;
may help. Second, variable interpolation requires double quoted strings:
my $file_location = "/network/$custom_directory/$custom_filename";
Third, you may probably need a \n at the print statement:
print $file "$variable_data\n";
And finally, your open statement should be:
open my $file, ">", $file_location or die $!;