selami
selami

Reputation: 89

Perl Error: No such file or directory

I have a few sub-folders in the main folder. There is a .txt file in each sub-folder. Firstly the code will create the "result" folder in the main folder. And it will search "atom" word in the .txt and print to the output file (in the result folder) as a column the columns including "Frequencies --" word. It will do this processes for each .txt file in sub-folders. The output file names can be the same to sub-folders. The code creates the "result" folder but it gives the following error. How can I fix it?

Error:Failed to open "./freq.txt" for writing: No such file or directory at ./analysis.pl line 27

#!/usr/bin/env perl
use strict;
use warnings; 
use File::Path qw/make_path/;
use Cwd;

my $dir = cwd();
opendir (my $dh, $dir) or die "Unable to open current directory! $!\n";
my @subdirs = grep { /^\.\.?\z/ } readdir($dh) or die "Unable to read directory! $!\n";
closedir $dh;

my $result_path = "$dir/results";
make_path("$result_path");

for my $subdir ( sort @subdirs ) {
    chdir($dir);
    next unless -d $subdir;
    make_path("$result_path/$subdir");
    my $search_text1 = qr/Frequencies --/;
    my $infile1="$subdir/freq.txt";
    my $outfile1="$result_path/output.txt";
# Line 27 below
    open my $in_fh1, '<', $infile1 or die qq{Failed to open "$infile1" for writing: $!};
    open my $out_fh1,'>', $outfile1 or die qq{Failed to open "$outfile1" for writing: $!};
    while (<$in_fh1>) {
        next unless /$search_text1/;
        my @fields1 = split;
        print $out_fh1 join("\t", $fields1[1,2,3]), "\n";
    }
    close($in_fh1);
    close($out_fh1);
    chdir("..");
}

Input file:

Frequencies --    23.5214                40.9023                56.7856
Red. masses --     6.7793                 1.0546                 5.5674
Frc consts  --     0.0022                 0.0010                 0.0106
IR Inten    --     2.4504                 0.2236                 0.6152
Atom AN      X      Y      Z        X      Y      Z        X      Y      Z
 1   6     0.00   0.01   0.06     0.00   0.00   0.01     0.00  -0.01   0.11
 2   6     0.00   0.00   0.09     0.00   0.00   0.01     0.00   0.00   0.10
 3   7     0.00   0.01   0.19     0.00   0.00   0.03     0.00   0.00   0.02
Frequencies --    91.1714                97.2522               123.2844
Red. masses --     7.3071                 9.6551                 6.3036
Frc consts  --     0.0358                 0.0538                 0.0564
IR Inten    --     0.5639                11.9103                 2.8105
Atom AN      X      Y      Z        X      Y      Z        X      Y      Z
 1   6     0.01  -0.14  -0.01    -0.01   0.04   0.04     0.00   0.00  -0.23
 2   6     0.00  -0.12   0.02     0.00   0.04   0.02     0.01   0.01   0.17
 3   7     0.00  -0.14  -0.05     0.00   0.04  -0.22     0.01   0.00  -0.15

Upvotes: 2

Views: 14111

Answers (1)

Schwern
Schwern

Reputation: 165416

Your code has a number of problems.

Your error message is incorrect. You're reporting you tried to open a file for writing when it was opened for reading. Reformatted, it's a bit easier to read.

# This says it's open for writing, but its for reading.
open my $in_fh1, '<', $infile1
    or die qq{Failed to open "$infile1" for writing: $!};
open my $out_fh1, '>', $outfile1
    or die qq{Failed to open "$outfile1" for writing: $!};

Your problem is freq.txt does not exist.

autodie will make all your IO functions throw good error messages. It's been shipped with Perl for a while now. Simply put use autodie in your script, usually near use strict and such, and you no longer have to write or die ... all over the place.

use strict;
use warnings; 
use autodie;

...later on...

open my $in_fh1, '<', $infile1;
open my $out_fh1, '>', $outfile1;

This line is also a problem.

my @subdirs = grep { /^\.\.?\z/ } readdir($dh) or die "Unable to read directory! $!\n";

readdir doesn't need to be checked if opendir was successful (which will be done with autodie now). The bigger problem is @subdirs will contain only . and .. which I doubt you wanted. You need to negate that match. It also helps to use m{} to avoid leaning toothpick syndrome.

opendir(my $dh, $dir);
my @subdirs = grep { !m{^\.\.?\z} } readdir($dh);
closedir $dh;

Path::Tiny will take care of this work for you. It doesn't come with Perl, but it's very easy to install and extremely useful. It can reduce that code to just...

my @subdirs = path(".")->children;

Path::Tiny is so useful, it will eliminate a large chunk of your code. No longer do you need to load one module to make a directory and another to find out what directory you're in and another to manipulate paths and another to... you get the idea. It also will throw errors on failure, so you don't need to write or die on everything.

Next problem is you have the line chdir($dir) but $dir is the current working directory so that does nothing. Later in the loop you chdir("..") so you'll be slowly climbing the directory tree. I don't know what your intention was. It's generally more robust to chdir to specific absolute directories than relative ones. If your code loses track of it's position, it will fix itself.

I don't see a need for you to be changing directories, so I'll just eliminate it.

Putting it all together, and using Path::Tiny where applicable...

#!/usr/bin/env perl
use strict;
use warnings;
use autodie;
use Path::Tiny;

# Get the current directory.  We could just use "." but this
# has the nice effect of giving us an absolute directory in case
# we chdir.
my $dir = Path::Tiny->cwd();

# Create a directory to hold the results.
my $result_path = $dir->child("results");
$result_path->mkpath;

# Loop through the sub directories.
for my $subdir ( sort $dir->children ) {
    next unless -d $subdir;

    # Make a matching sub-directory in the results directory
    # I believe there is a mistake, this directory is never
    # used.  I suspect output.txt needs to go in here.
    $result_path->child($subdir)->mkpath;

    # Open the frequency file and an output file for it.
    # I believe there is a mistake here, each iteration of the
    # loop will overwrite output.txt.  I believe it was
    # supposed to use the directory created above.
    my $infile1  = $subdir->child("freq.txt");
    my $outfile1 = $result_path->child("output.txt");

    # Open them for reading and writing.
    my $in_fh1  = $infile1->openr;
    my $out_fh1 = $outfile1->openw;

    # Process just the Frequencies line.
    while (<$in_fh1>) {
        next unless /Frequencies --/;

        # There is a mistake here, this will split "Frequencies --"
        # into two parts.  You want @fields1[2,3,4] or better yet
        # strip the header with "next unless s/^Frequencies --//;"
        my @fields1 = split;

        # An array slice uses @.  It should be @fields1[...]
        print $out_fh1 join("\t", $fields1[1,2,3]), "\n";
    }
}

There are still many mistakes in the processing. I've noted them in comments, but this answer is already far too long.

Upvotes: 3

Related Questions