Why does my chdir to a filehandle not work in Perl? - perl

When I try a "chdir" with a filehandle as argument, "chdir" returns 0 and a pwd returns still the same directory. Should that be so?
I tried this, because in the documentation to chdir I found:
"On systems that support fchdir, you
might pass a file handle or directory
handle as argument. On systems that
don't support fchdir, passing handles
produces a fatal error at run time."
Given later:
#!/usr/bin/perl -w
use 5.010;
use strict;
use Cwd;
say cwd(); # /home/mm
open( my $fh, '>', '/home/mm/Documents/foto.jpg' ) or die $!;
say chdir $fh; # 0
say cwd(); # /home/mm
I thought that this would maybe chdir to the directory of the file - but no DWIM for me here.

It also says
It returns true upon success, false otherwise.
meaning that your call to chdir failed. Check the $! variable for a clue about what happened. Since you didn't get a fatal runtime error, you don't have to worry about that last paragraph about fchdir.
Running a couple of tests, I see chdir FILEHANDLE works when FILEHANDLE refers to a directory, but not to a regular file. Hope that helps:
open(FH, "<", "/tmp/file"); # assume this file exists
chdir FH and print "Success 1\n" or warn "Fail 1: $!\n";
open(FH, "<", "/tmp");
chdir FH and print "Success 2\n" or warn "Fail 2: $!\n";
opendir(FH, "/tmp");
chdir FH and print "Success 3\n" or warn "Fail 3: $!\n";
Fail 1: Not a directory
Success 2
Success 3

Which version of perl? Which operating system?
5.10.1 on Windows:
#!/usr/bin/perl
use strict; use warnings;
# have to use a file because Windows does not let
# open directories as files
# only done so I can illustrate the fatal error on
# a platform where fchdir is not implemented
open my $fh, '<', 'e:/home/test.txt'
or die "Cannot open file: $!";
chdir $fh
or die "Cannot chdir using filehandle: $!";
Output:
C:\Temp> k
The fchdir function is unimplemented at C:\Temp\k.pl line 9.
5.10.1 on Linux (/home/sinan/test is a directory):
$ cat k.pl
#!/usr/bin/perl
use strict; use warnings;
use Cwd;
open my $fh, '<', '/home/sinan/test'
or die "Cannot open file: $!";
chdir $fh
or die "Cannot chdir using filehandle: $!";
print getcwd, "\n";
$ ./k.pl
/home/sinan/test

Works for me. Windows doesn't support fchdir and it is in fact a fatal error there:
perl -we"opendir my $fh, 'temp'; chdir $fh or print 'foo'"
produces a fatal error. So it looks like on systems that have no support for fchdir at all it works to spec. Looks like the wording could be cleared up especially the word "might."

Related

In Perl script, I can open / write to/ and close a file, but I get "bad file descriptor" when I try to flock it

I can OPEN the file using the file handle, but when I try to FLOCK using the same file handle I get "bad file descriptor."
my $file='/Library/WebServer/Documents/myFile.txt';
open(my $fh, '>', $file) or die "Could not open '$file' - $!";
# I DO NOT GET AN ERROR FROM OPENING THE FILE
flock($fh, LOCK_EX) or die "Could not lock '$file' - $!";
# HERE IS WHERE I GET THE "BAD FILE DESCRIPTOR" ERROR
# IF I COMMENT THIS LINE OUT, THE PRINT AND CLOSE COMMANDS BELOW EXECUTE NORMALLY
print $fh "hello world";
close($fh) or die "Could not write '$file' - $!";
It's the same file handle, so why do OPEN and PRINT work, but not FLOCK? I have tried setting the permissions for the file to 646, 666, and 777, but I always get the same results.
Thanks!
Did you import the constant LOCK_EX per the flock documentation?
use Fcntl ':flock';
If not, LOCK_EX doesn't mean anything and the flock call will fail. Using strict and/or warnings would have identified a problem with this system call.

What is the easiest way to test error handling when writing to a file in Perl?

I have a bog standard Perl file writing code with (hopefully) adequate error handling, of the type:
open(my $fh, ">", "$filename") or die "Could not open file $filname for writing: $!\n";
# Some code to get data to write
print $fh $data or die "Could not write to file $filname: $!\n";
close $fh or die "Could not close file $filname afterwriting: $!\n";
# No I can't use File::Slurp, sorry.
(I just wrote this code from memory, pardon any typos or bugs)
It is somewhat easy to test error handling in the first "die" line (for example, create a non-writable file with the same name you plan to write).
How can I test error handling in the second (print) and third (close) "die" lines?
The only way I know of to induce error when closing is to run out of space on filesystem while writing, which is NOT easy to do as a test.
I would prefer integration test type solutions rather than unit test type (which would involve mocking IO methods in Perl).
Working with a bad filehandle will make them both fail
use warnings;
use strict;
use feature 'say';
my $file = shift || die "Usage: $0 out-filename\n";
open my $fh, '>', $file or die "Can't open $file: $!";
$fh = \*10;
say $fh 'writes ok, ', scalar(localtime) or warn "Can't write: $!";
close $fh or warn "Error closing: $!";
Prints
say() on unopened filehandle 10 at ...
Can't write: Bad file descriptor at ...
close() on unopened filehandle 10 at ...
Error closing: Bad file descriptor at ...
If you don't want to see perl's warnings capture them with $SIG{__WARN__} and print your messages to a file (or STDOUT), for example.
Riffing on zdim's answer ...
Write to a file handle opened for reading.
Close a file handle that has already been closed.

how do i redirect output to excel file from console through perl script?

#!/usr/bin/perl
use strict;
use autodie;
use Capture::Tiny 'capture_merged';
my $ID=732636;
my $User="vispsh1";
my $Password="monitor1";
print "viewing item...\n";
open (my $file, '>', 'output.xls') or die "cannot open file";
my $mks_cmd="im viewissue --user='$User' --password='$Password' $ID";
print $file capture_merged { system($mks_cmd)};
# print $mks_cmd;
print "Done\n";
This is my script, the file is created but, it is not redirecting anything to output.xls, i can do it by using below command
C:\perl>perl -w mks.pl > sample.xls
but, i want to do it through the script to automate it fully!
Please help, Thankyou.

unable to generate error and redirect it to a file in perl

I am trying to redirect my STDOUT and STDERR to some file. I am successful with that to some extent. But i am not able to understand one thing in the below code.
#!/usr/bin/perl
open (STDOUT,">/var/tmp/outfile") or die "problem : $!";
open (STDERR,">>/var/tmp/outfile") or die "problem : $!";
print "$_\n" foreach (1..10);
sdsdf; # buggy line inserted wantedly
I have inserted the last line assuming that perl would throwout an error and that would be redirected to the file but its not happening . My program does not throughout any error onto the screen nor to the outfile. Please help me understand this behavior.
The sdsdf is not generating any errors (if you use strict then you'll see some compile time errors), that's why you are not seeing any messages. Try this:
use warnings;
use strict;
open (STDOUT,">outfile1") or die "problem : $!";
open STDERR, ">&STDOUT";
print "$_\n" foreach (1..10);
die("aaaa"); # buggy line inserted wantedly
Also in your code you are opening the same file twice, this might cause some problems. In the above we first redirect the stdout to a file then redirect stderr to stdout.
Without use strict;,
sdsdf;
is the same as
"sdsdf";
That's one of the reasons you always want to use use strict; use warnings;. Let's start by adding that.
So you want to log all output including compile-time errors to a file. Well, that's not going to happen by redirecting STDERR after your code has been compiled. The best way to do this is from outside your program.
script.pl >/var/tmp/outfile 2>&1
but it can be done from within your program.
#!/usr/bin/perl
use strict;
use warnings;
BEGIN {
open(STDOUT, '>', '/var/tmp/outfile')
or die("Can't redirect STDOUT: $!\n");
open(STDERR, '>&', \*STDOUT)
or die("Can't redirect STDERR: $!\n");
}
print "$_\n" foreach (1..10);
sdsdf; # Syntax error

Write to a file in Perl

Consider:
#!/usr/local/bin/perl
$files = "C:\\Users\\A\\workspace\\CCoverage\\backup.txt";
unlink ($files);
open (OUTFILE, '>>$files');
print OUTFILE "Something\n";
close (OUTFILE);
The above is a simple subroutine I wrote in Perl, but it doesn't seem to work. How can I make it work?
Variables are interpolated only in strings using double quotes ". If you use single quotes ' the $ will be interpreted as a dollar.
Try with ">>$files" instead of '>>$files'
Always use
use strict;
use warnings;
It will help to get some more warnings.
In any case also declare variables
my $files = "...";
You should also check the return value of open:
open OUTFILE, ">>$files"
or die "Error opening $files: $!";
Edit: As suggested in the comments, a version with the three arguments open and a couple of other possible improvements
#!/usr/bin/perl
use strict;
use warnings;
# warn user (from perspective of caller)
use Carp;
# use nice English (or awk) names for ugly punctuation variables
use English qw(-no_match_vars);
# declare variables
my $files = 'example.txt';
# check if the file exists
if (-f $files) {
unlink $files
or croak "Cannot delete $files: $!";
}
# use a variable for the file handle
my $OUTFILE;
# use the three arguments version of open
# and check for errors
open $OUTFILE, '>>', $files
or croak "Cannot open $files: $OS_ERROR";
# you can check for errors (e.g., if after opening the disk gets full)
print { $OUTFILE } "Something\n"
or croak "Cannot write to $files: $OS_ERROR";
# check for errors
close $OUTFILE
or croak "Cannot close $files: $OS_ERROR";