Unable to understand reason for Tie::File failure - perl

I have the following code
#!/usr/bin/perl
use Tie::File;
tie my #last_id, 'Tie::File', 'last_id.txt' or die "Unable to open this file !$i";
print #last_id[0];
exit;
and a file named last_id.txt with something like this in it
1
2
3
4
5
6
When I run the program nothing gets output. I tried $last_id[0] but still nothing. :/
I have latest ActivePerl installed.
EDIT:
Now I get the Unable to open this file message, but the file exists in the same directory as the program source file.

As you say, #last_id[0] is wrong and should be $last_id[0]. But this won't cause the problem you are seeing.
Note that the program won't look for last_id.txt in the same directory as the Perl source file unless it it also the current working directory.
You should first of all change the error variable used in the tie line to $! like this
tie my #last_id, 'Tie::File', 'last_id.txt'
or die "Unable to open this file: $!";
as the variable $i contains nothing useful. This will tell you the reason for the failure of tie, which may be something other than no such file or directory.
You should also use strict and use warnings at the start of your program, as this will flag simple errors that are easy to overlook.
Finally, try fully-qualifying the filename by adding an absolute path to it. That will get around the problem of the program looking in the wrong directory by default.
Update
If the problem is that you have no write access to the file then you can fix it by opening it read-only.
You need the Fcntl module to define the O_RDONLY constant, so put this at the top of your program
use Fcntl 'O_RDONLY';
then the tie statement goes like this
tie my #last_id, 'Tie::File', 'last_id.txt', mode => O_RDONLY
or die "Unable to open this file: $!";

The problem should go away if you use absolute paths
BEGIN {
use File::Spec;
use File::Basename qw' dirname ';
use vars qw' $thisfile $thisdir ';
$thisfile = File::Spec->rel2abs(__FILE__);
$thisdir = dirname($thisfile);
}
...
tie ... "$thisdir/last_id.txt"

Related

Perl File pointers

I have a question concerning these two files:
1)
use strict;
use warnings;
use v5.12;
use externalModule;
my $file = "test.txt";
unless(unlink $file){ # unlink is UNIX equivalent of rm
say "DEBUG: No test.txt persent";
}
unless (open FILE, '>>'.$file) {
die("Unable to create $file");
}
say FILE "This name is $file"; # print newline automatically
unless(externalModule::external_function($file)) {
say "error with external_function";
}
print FILE "end of file\n";
close FILE;
and external module (.pm)
use strict;
use warnings;
use v5.12;
package externalModule;
sub external_function {
my $file = $_[0]; # first arguement
say "externalModule line 11: $file";
# create a new file handler
unless (open FILE, '>>'.$file) {
die("Unable to create $file");
}
say FILE "this comes from an external module";
close FILE;
1;
}
1; # return true
Now,
In the first perl script line 14:
# create a new file handler
unless (open FILE, '>>'.$file) {
die("Unable to create $file");
}
If I would have
'>'.$file
instead, then the string printed by the external module will not be displayed in the final test.txt file.
Why is that??
Kind Regards
'>' means open the file for output, possibly overwriting it ("clobbering"). >> means appending to it if it already exists.
BTW, it is recommended to use 3 argument form of open with lexical file-handles:
open my $FH, '>', $file or die "Cannot open $file: $!\n";
If you use >$file in your main function, it will write to the start of the file, and buffer output as well. So after your external function returns, the "end of file" will be appended to the buffer, and the buffer flushed -- with the file pointer still at position 0 in the file, so you'll just overwrite the text from the external function. Try a much longer text in the external function, and you'll see that the last part of it remains, with the first part getting overwritten.
This is very old syntax, and not recommended in modern Perl. The 3-argument version, which was "only" introduced in 5.6.1 about 10 years ago, is preferred. So is using a lexical variable for a file handle, rather than an uppercase bareword.
Anyway, >> means open for append, whereas > means open for write, which will remove any existing data in the file.
You're clobbering your file when you reopen it once more. The > means open the file for writing, and delete the old file if it exists and create a new one. The >> means open the file for writing, but append the data if the file already exists.
As you can see, it's very hard to pass FILE back and forth between your module and your program.
The latest syntax is to use lexically scoped variables for file handles:
use autodie;
# Here I'm using the newer three parameter version of the open statement.
# I'm also using '$fh' instead of 'FILE' to store the pointer to the open
# file. This makes it easier to pass the file handle to various functions.
#
# I'm also using "autodie". This causes my program to die due to certain
# errors, so if I forget to test, I don't cause problems. I can use `eval`
# to test for an error if I don't want to die.
open my $fh, ">>", $file; # No die if it doesn't open thx to autodie
# Now, I can pass the file handle to whatever my external module needs
# to do with my file. I no longer have to pass the file name and have
# my external module reopen the file
externalModule::xternal_function( $fh );

Irregular Behavior with Perl print

I'm attempting to print out to two different files. For some reason, print statements work fine for one file, but not for the other. When I run this program, filter2.out consists of a single line that reads "Beginning". filter2.err remains empty.
open(OUTPUT, "> Filter2/filter2.out");
open(ERROR, "> Filter2/filter2.err");
print OUTPUT "Beginning\n";
print ERROR "Beginning\n";
UPDATE: So I was running this at the beginning of a larger program and realized that it only updates the ERROR file in batches or when the file is closed. Any idea why this occurs?
Consider adding
use strict;
use warnings;
to the top of your script. These statements will help catch errors that are otherwise silently ignored by Perl. In addition, consider adding error checking to your open calls: in all likelihood, it's not actually opening. I'd write it like this:
use strict;
use warnings;
open(OUTPUT, "> Filter2/filter2.out")
or die "Can't open filter2.out: $!";
open(ERROR, "> Filter2/filter2.err")
or die "Can't open filter2.err: $!";
print OUTPUT "Beginning\n";
print ERROR "Beginning\n";
for example, by just adding adding strict and warnings I got:
print() on closed filehandle OUTPUT at .\printer.pl line 6.
print() on closed filehandle ERROR at .\printer.pl line 7.
Hmm...!
By adding error checking, I got:
PS C:\dev> perl .\printer.pl
Can't open filter2.out: No such file or directory at .\printer.pl line 4.
Aah! Looking, I didn't have the folder. After I added the folder, everything ran fine. You'll probably find something similar.
Finally, you should probably also use the modern, lexical file handles. This helps catch other errors (like re-used handle names.) Thus, the final script would look like:
use strict;
use warnings;
open(my $output, ">", "Filter2/filter2.out")
or die "Can't open filter2.out: $!";
open(my $error, ">", "Filter2/filter2.err")
or die "Can't open filter2.err: $!";
print $output "Beginning\n";
print $error "Beginning\n";
Viola! Now you can see exactly where the problem fails, as it fails, and make sure that other libraries or code you write later can't accidentally interfere with your file handles.
You need to check that your files were properly opened. Also it's better to use local variables as file handles instead of bare words:
open( my $err, "> Filter2/filter2.err") or die "Couldn't open error: $!"
print $err "Beginning\n"

Error when running a DOS command in CGI

I tried to run a simple copy of one file to another folder using Perl
system("copy template.html tmp/$id/index.html");
but I got the error error: The syntax of the command is incorrect.
When I change it to
system("copy template.html tmp\\$id\\index.html");
The system copies another file to the tmp\$id foler
Can someone help me?
I suggest you use File::Copy, which comes with your Perl distribution.
use strict; use warnings;
use File::Copy;
print copy('template.html', "tmp/$id/index.html");
You do not need to worry about the slashes or backslashes on Windows because the module will take care of that for you.
Note that you have to set relative paths from your current working directory, so both template.html as well as the dir tmp/$id/ needs to be there. If you want to create the folders on the fly, take a look at File::Path.
Update: Reply to comment below.
You can use this program to create your folders and copy the files with in-place substitution of the IDs.
use strict; use warnings;
use File::Path qw(make_path);
my $id = 1; # edit ID here
# Create output folder
make_path("tmp/$id");
# Open the template for reading and the new file for writing
open $fh_in, '<', 'template.html' or die $!;
open $fh_out, '>', "tmp\\$id\index.html" or die $!;
# Read the template
while (<$fh_in>) {
s/ID/$id/g; # replace all instances of ID with $id
print $fh_out $_; # print to new file
}
# Close both files
close $fh_out;
close $fh_in;

How do I create a directory in Perl?

I am new to Perl and trying to write text files. I can write text files to an existing directory no problem, but ultimately I would like to be able to create my own directories.
I am going to download files from my course works website and I want to put the files in a folder named after the course. I don't want to make a folder for each course manually beforehand, and I would also like to eventually share the script with others, so I need a way to make the directories and name them based on the course names from the HTML.
So far, I have been able to get this to work:
use strict;
my $content = "Hello world";
open MYFILE, ">C:/PerlFiles/test.txt";
print MYFILE $content;
close (MYFILE);
test.txt doesn't exist, but C:/PerlFiles/ does and supposedly typing > allows me to create files, great.
The following, however does not work:
use strict;
my $content = "area = pi*r^2";
open MYFILE, ">C:/PerlFiles/math_class/circle.txt";
print MYFILE $content;
close (MYFILE);
The directory C:/PerlFiles/math_class/ does not exist.
I also tried sysopen but I get an error when adding the flags:
use strict;
my $content = "area = pi*r^2";
sysopen (MYFILE, ">C:/PerlFiles/math_class/circle.txt", O_CREAT);
print MYFILE $content;
close (MYFILE);
I got this idea from the Perl Cookbook chapter 7.1. Opening a File. It doesn't work, and I get the error message Bareword "O_CREAT" not allowed while "strict subs" in use. Then again the book is from 1998, so perhaps O_CREAT is obsolete. At some point I think I will need to fork over the dough for an up-to-date version.
But still, what am I missing here? Or do the directories have to be created manually before creating a file in it?
Right, directories have to be created manually.
Use mkdir function.
You can check if directory already exists with -d $dir (see perldoc -f -X).
Use File::Path to create arbitrarily deep paths. Use dirname to find out a file's containing directory.
Also, use lexical file handles and three-argument open:
open(my $fd, ">", $name) or die "Can't open $name: $!";

How can I create a path with all of its subdirectories in one shot in Perl?

If you have a path to a file (for example, /home/bob/test/foo.txt) where each subdirectory in the path may or may not exist, how can I create the file foo.txt in a way that uses "/home/bob/test/foo.txt" as the only input instead of creating every nonexistent directory in the path one by one and finally creating foo.txt itself?
You can use File::Basename and File::Path
use strict;
use File::Basename;
use File::Path qw/make_path/;
my $file = "/home/bob/test/foo.txt";
my $dir = dirname($file);
make_path($dir);
open my $fh, '>', $file or die "Ouch: $!\n"; # now go do stuff w/file
I didn't add any tests to see if the file already exists but that's pretty easy to add with Perl.
Use make_dir from File::Util
use File::Util;
my($f) = File::Util->new();
$f->make_dir('/var/tmp/tempfiles/foo/bar/');
# optionally specify a creation bitmask to be used in directory creations
$f->make_dir('/var/tmp/tempfiles/foo/bar/',0755);
I don't think there's a standard function that can do all of what you ask, directly from the filename.
But mkpath(), from the module File::Path, can almost do it given the filename's directory. From the File::Path docs:
The "mkpath" function provides a
convenient way to create directories,
even if your "mkdir" kernel call won't
create more than one level of
directory at a time.
Note that mkpath() does not report errors in a nice way: it dies instead of just returning zero, for some reason.
Given all that, you might do something like:
use File::Basename;
use File::Path;
my $fname = "/home/bob/test/foo.txt";
eval {
local $SIG{'__DIE__'}; # ignore user-defined die handlers
mkpath(dirname($fname));
};
my $fh;
if ($#) {
print STDERR "Error creating dir: $#";
} elsif (!open($fh, ">", $fname)) {
print STDERR "Error creating file: $!\n";
}