Re: Best way to handle readline errors?
by ikegami (Patriarch) on Nov 11, 2006 at 03:13 UTC
|
I did some digging (on Windows XP, using perl 5.8.8). I didn't come up with a solution, but I thought I'd post what I found out.
-
I can get <$fh> to return undef by ejecting the media on which resides the file being read. close reports no error. $! is meaningless (It's "Bad file descriptor"/9 when the read is successful and when the read is not successful.)
-
I cannot get <$fh> (with $/ = \100) to return when the media on which resides the file being read has been ejected. It just hangs. Reinserting the media doesn't help.
-
I can get sysread to return undef by ejecting the media on which resides the file being read. close reports no error.
-
I can get read to return undef by ejecting the media on which resides the file being read. close reports "Bad file descriptor".
In all cases, I can confirm an IO error occured, since the OS pops a dialog asking to reinsert the disk once all read buffers have been emptied.
Test programs:
| [reply] [d/l] [select] |
|
|
ikegami, I liked your approach (ejecting a CD to cause a read error). So, here are my results which are somewhat different than yours:
- All three versions read 99Mb after the disk was ejected, indicating that WinXP must be caching the CD file
- sysread returned undef but $! was set to 0!
- Failures in sysread and readline did not cause close to fail whereas read failure did.
- In my tests, readline did fail, similar to read, as expected.
The results, run on WinXP cygwin using ActiveState Perl for Windows v5.8.7:
Here are the results when I set $initial_read to 0:
Here's my test code:
| [reply] [d/l] [select] |
|
|
All three versions read 99Mb after the disk was ejected, indicating that WinXP must be caching the CD file
It did that to me when I used Alcohol to mount and unmount a virtual CD instead of ejecting a real CD. Apparently, Alcohol doesn't fully unmount an image with an open file handle.
When I switched to using a real CD, WinXP would prompt me to reinsert the disk when all cached data had been read. When I Cancel that dialog, Perl returns an error.
| [reply] |
|
|
So, can anyone help with my original question? I'm sure I'm not the only perl person who is concerned with thorough error handling in code where error handling is important (such as code run by root).
The original question was: what is the Perl Best Practice idiom for handling readline errors?
Being a new poster to PerlMonks, but not a new perl user, is there anything I can do or any way I can help to get this question answered? Anything I can do to get more attention focused on this question?
| [reply] [d/l] |
|
|
Thanks, ikegami! That was useful.
It's worrying that <$fh> won't return when you eject and/or reinsert the media. What's going on down in the bowels of that code?
sysread and read were planned in a way which takes errors into consideration (patterned after read in c). But error handling in readline seems to be somewhat of an afterthought. It makes me wonder if I have to reimplement readline at the perl level using read (Ugh!).
I started asking this question because it's hard for me to justify writing code which will run as root that doesn't have complete error checking, including checking for input errors. Oh, Damian, why didn't Perl Best Practices should us lowly monks how to handle readline errors, too?
| [reply] [d/l] [select] |
Re: Best way to handle readline errors?
by lin0 (Curate) on Nov 11, 2006 at 00:17 UTC
|
Update:
I misread the question. So this answer does not apply. I apologize for all the noise I introduced with my answers
Regards
lin0
Hi jrw,
did you try the following?
while ( defined($data = <$fh>) ) {
# do something ...
# usually: chomp($data);
}
Cheers!
lin0 | [reply] [d/l] |
|
|
That won't tell me if I had an error or if I reached EOF. I want to distinguish those two cases.
| [reply] |
|
|
Update
I just want to withdraw my answer to the question. I want to apologize to the OP for not answering the question and adding noise in the process. On my defense, I wanted to help but I guess I was too tired to think clearly
All the best
lin0
Hi jrw,
maybe you are interested in something like this piece of code I found and adapted from the perldoc for readline
for (;;) {
undef $!;
unless (defined( $data = <$fh> )) {
die $! if $!;
last; # reached EOF
}
# do what you need to do with your data
# like:
# chomp($data);
# ...
}
I hope this helps
lin0 | [reply] [d/l] |
|
|
|
|
Re: Best way to handle readline errors?
by graff (Chancellor) on Nov 11, 2006 at 15:16 UTC
|
undef $! doesn't really undefine $! because defined($!) returns true after undef($!) !!
Huh? How do you get that? Doesn't work that way for me...
perl -e 'print "Output:\n"; for $i (1..3) {
print "$i: ".(defined($_)? "defined\n":"not defined\n");
$_ = ($i==1) ? "":undef
}'
Output:
1: not defined
2: defined
3: not defined
(perl 5.8.6 on macosx/darwin)
$_ starts out as undefined until something other than undef is assigned to it, and after that, assigning undef to it appears to have the expected result.
I would think most 5.* perl versions to behave the same in this regard, so maybe you were doing things differently?
(update: oops! Sorry, I got confused about which variable was being talked about there. Thanks to jrw for setting me straight.)
warnings from <readline> sent to stderr will have to be handled in some way
Right. Open STDERR to a scalar:
open( STDERR, ">", \$stderr_log );
It might suffice just to do that, and check the length of that scalar after each "readline" operation. (If it's not empty, do something about it; and if it's appropriate to continue, reset it to an empty string before continuing.)
Also, if readline fails and returns false, you have eof($fh) that will also return false if the readline failure was due to an error (rather than end-of-file). Are you saying that you are having some problem getting that to work the way you want? (You haven't mentioned what OS you're using, or what sorts of errors you're worried about...) | [reply] [d/l] [select] |
|
|
perl -e 'undef $!; print "just kidding\n" if defined $!'
This prints "just kidding" on AIX 5.2 (perl 5.8.0 and 5.005_03), cygwin (perl 5.8.7), linux 2.4.2 (perl 5.6.0), Solaris 5.9 (perl 5.6.1, 5.6.0, 5.005_03).
What I'm looking for is general advice which allows me to add error checking to all my calls to readline or <$fh>. Here are my "requirements": a minimal amount of ugly code, relatively efficient, independent of the rest of my program (that rules out using the "send STDERR to scalar" trick). It would also be nice if the technique worked at least as far back as perl 5.005_03.
$fh->eof is best avoided if $fh might be attached to a terminal, which in the general case it might be.
| [reply] [d/l] [select] |
Re: Best way to handle readline errors?
by ikegami (Patriarch) on Nov 10, 2006 at 23:39 UTC
|
If you can live with the file getting closed,
while (<$fh>) {
...
}
close($fh)
or die("Error reading from input file: $!\n");
Or does that only work for output files?
| [reply] [d/l] |
|
|
The doc says "Closes the file or pipe associated with the file handle, returning true only if IO buffers are successfully flushed and closes the system file descriptor. Closes the currently selected filehandle if the argument is omitted."
So, I don't think that will work for input files.
| [reply] |
|
|
| [reply] [d/l] |
Re: Best way to handle readline errors?
by Firefly258 (Beadle) on Nov 14, 2006 at 15:22 UTC
|
Working on the basis of $! is "russian-roulette" with you guaranteed to lose everytime, it's value can be anything when the expression it is associated with succeeds.
$ perl -le 'print $c++ for 0..0xFFFF' > test; ls -l test
-rwxr-xr-x 1 user user 382106 2006-11-14 15:04 test
$ perl -e 'open F, "test"; print "$! => $_" while <F>'
Bad file descriptor => 0
Bad file descriptor => 1
Bad file descriptor => 2
...
Infact, undefining $! has no effect and perlvar does say "the value of $! is meaningful only immediately after a failure". Like $^E, its useful for one thing only - describing the error that occured. Programming around it would definitely be considered "bad practice".
Just use defined or eval to test the success of readline().
#!/usr/bin/perl -Wl
use strict;
use warnings;
$|++;
open F, "<", $ARGV[0];
while (1) {
last if eof(F);
if (defined chomp( local $_ = readline (F) )) {
print
} else {
warn "hmerrm, readline failed : $!";
last;
}
}
# or
while (1) {
last if eof(F);
chomp( local $_ = eval "readline (F)" );
unless ($@) {
print;
} else {
warn "whoops, readline failed : $@";
last;
}
}
| [reply] [d/l] [select] |
|
|
Firefly258, are you saying that perldoc readline is wrong? From perldoc readline:
for (;;) {
undef $!;
unless (defined( $line = <> )) {
die $! if $!;
last; # reached EOF
}
# ...
}
That clearly indicates that EOF and a readline error can be distinguished by undefining $! before the call and then, if readline failed, checking if $! is set to non-0. | [reply] [d/l] [select] |
|
|
I should have said this before to make it a little clearer.
Read the contents of $! to glean the exact cause of an error right after an unsuccessful operation, Do not test $! to determine the success of an operation, use the TRUE/FALSE value returned instead. The Menmonic of $! is -- "What just went bang?" not "Did I just hear bang?", There is an obvious difference.
Your reading of the operation concerning $! is definitely right but the reading concerning the context in which it is done is not. Here, if <> returns undef to $line, defined returns FALSE to unless () which puts the execution control in its block, it is there and only there where $! is current and valid.
E.g
unless (defined( $line = <> )) {
die $! if $!; # $! is only current and valid in the afterma
+th of a failure
last; # reached EOF
}
is not the same as
$line = <>;
if ($!) {
print "Ohh noo, $!"; # an indefinite false-positive
}
if you should ask why?? Consider the following..
$ touch test; # create the dummy
$ perl -Mstrict -Wle 'undef $!; open my $fh, "<", "test" or warn "warn
+ : $!"; print "after : $!"'
after : Inappropriate ioctl for device
$ rm test;
$perl -Mstrict -Wle 'undef $!; open my $fh, "<", "test" or warn "warn
+: $!"; print "after : $!"
warn : No such file or directory at -e line 1.
after : No such file or directory
In the first operation, open succeeds and returns a TRUE value so warn() is skipped. However in the second, open fails and returns FALSE, warn is triggered spitting out a valid $!. The $! outside the expression is not trustworthy as one would infer from the documentation of $! in perlvar.
update
a readline error can be distinguished by undefining $! before the call
undefining $! is a useless, almost redundant operation IMO, consequetive operations will set $! if they need to, sometimes regardless of whether they succeed or fail.
checking if $! is set to non-0
It's more like, checking if $! is TRUE, because FALSE can be representation of not just 0 but undef, "" (blank) or "0"; | [reply] [d/l] [select] |
|
|
|
|
|