bluethundr has asked for the wisdom of the Perl Monks concerning the following question:
Hey Monks,
I am just attemmpting to learn to filestat from the llama. I am working off of Chapter 11 in said book, and excercise 1 which states:
Make a program that takes a list of files named on the command line and reports for each one whether its readable, writable, executable, or doesn't exist. (Hint: It may be helpful to have a function that will do all of the file tests for one file at a time.) What does it report about a file that has been chmod-ed to 0? (That is if you are on a Unix system use the command chmod 0 some_file to mark that file as being neither readable, writable nor executable.) In most shells, use a star as the argument to mean all of the normal files in the current directory. That is you should type something like ./ex11-1 to ask the program for the attributes of many files at once.
Iam going to add the rest of the tests once I have it figured out what I'm doing wrong. But this is very preliminary and I don't know why the first test I added is not working. #!/usr/bin/perl
use warnings;
use strict;
sub filetest {
my @answer;
return "File does not exist"
unless -e $_;
}
while (<>) {
my @answer = &filetest($_);
}
This is the message I get when I try to run the program: Unsuccessful stat on filename containing newline at ./ex1-11 line 8, <> line 1Also, in the subroutine 'filetest' I have an (as yet) unused array called @answer. Once I have the file tests worked out I intend to fill that array with strings indicating the states of the file tests.
Once I have all the data collected I plan to return that array. Thanks for any help you'd care to dispense!
Re: Unsuccessful stat on file test
by Corion (Patriarch) on Mar 02, 2008 at 18:31 UTC
|
Unsuccessful stat on filename containing newline at ./ex1-11 line 8, <
+> line 1
means that you have a filename containing a newline. Which is perfectly explainable, because
while (<>) {
...
sets $_ to the entered line including the newline. Use the following to remove the newline and other whitespace from the end of the entered line:
s/\s*$//;
Alternatively, for a more fragile solution, look at the chomp function. | [reply] [d/l] [select] |
|
Thanks for the suggestion! I didn't even see the '\n' with my beginner's eyes! But now, my code reads like this:
#!/usr/bin/perl
use warnings;
use strict;
sub filetest {
my @answer;
return "File does not exist\n";
unless -e $_;
push @answer, "readable "
if -r $_;
push @answer, "writable "
if -w $_;
push @answer, "executable "
if -x $_;
return @answer;
}
while (<>) {
s/\s*$//;
my @answer = &filetest($_);
print @answer;
}
And now, no matter what file I feed the program, it reports "File does not exist". Also, if I remove the line return "File does not exist\n";
unless -e $_;
the program executes quietly, not printing out any messages. I'm a little confused and befuddled by this.
| [reply] [d/l] [select] |
|
return "File does not exist\n";
unless -e $_;
needs to become
return "File does not exist\n"
unless -e $_;
the program works for me:
C:\Projekte>perl -w tmp.pl
tmp.pl
readable writable
So, maybe tell us more exactly what you're typing in, what files exist, and maybe consider adding some more debugging output to your file.
Your form of calling the filetest function is bad/misleading. filetest is not using any parameters passed to it, it uses the global $_. So you should either call it as filetest(); or make your subroutine actually use its argument.
As a style note, I never use the ampersand notation for function calls (&filetest()) - I find it too dangerous, because when I leave off the parentheses (&filetest;), that has the unintended side effect of calling filetest with the current values of @_ instead of passing no parameters at all. | [reply] [d/l] [select] |
|
Your code is almost there, but you have to remove the semicolon:
my @answer;
return "File does not exist\n"; # <- extra semicolon!
unless -e $_;
The next problem is: while (<>) {
If your program is named filetest.pl and you call it like:./filetest.pl *.txt
that loop is actually going to read each line of all the *.txt files in your current directory, and unless each line actually happens to be the name of a file in that directory, your program will correctly tell you that the file does not exists!
Try adding some extra output (what is $_ ?)
cheers | [reply] [d/l] [select] |
|
|
for ( @ARGV ) {
my @answer = filetest( $_ );
print @answer;
}
| [reply] [d/l] [select] |
|
You should use s/\s+$//; instead of s/\s*$//;. s/\s*$//; will modify every string that it operates on (every string has zero whitespace in it) while s/\s+$//; will only modify strings that actually have whitespace at the end.
| [reply] [d/l] [select] |
|
Except that, as was the original problem, every string does have whitespace at the end: the EOL character.
Further, I'm not sure that even if some strings didn't have the EOL whitespace, such as the general case of using s/\s*$// to eliminate whitespace from the end of a string, that this is anything more than premature optimisation. For all I know, perl could already optimise that away, or it may be of no consequence even if it was still "performed". Do you have any evidence that your suggestion improves the OP's code?
Now, if you were to come in and say to use + instead of * because it more literally descriped what the OP was doing, I'd be in much more (but not complete) agreement. That is, the OP wants to replace all whitespaces leading up to the end of the line, qr/\s+$/, with nothing, whereas the code that is currently there could be read as saying that zero-length strings should be replaced, too, which seems silly. It is apparent to anyone with reasonable amounts of regexp-fu that we don't really care about deleting zero-length strings, so it just seems silly to try replacing them. (Conversely, since it's a "don't care" operation, fixing the code now that it's written and actually works doesn't need doing, either.)
I can even think of a pathological case where \s* is better than \s+ - and it's not optimisation (well, not of CPU cycles anyway).
my @filters = ( \&eliminate_ws, \&do_something, \&do_something_else, \
+&etc );
# ...
while (<$myfile>)
{
FILTERS: for my $filter (@filters)
{
# keep filtering until a filter says to stop.
last FILTERS unless $filter->()
}
}
sub eliminate_ws { s/\s*$// } # always returns true.
# as opposed to:
# sub eliminate_ws { s/\s+$//; 1 } # always returns true, even if s "f
+ailed"
It saves the developer a few keystrokes... but, like I said, it's pathological. ;-)
Update: given the test below, I'm even more convinced this is premature optimisation. You're doing 1000 tests per sub call, so asking perl what the savings is comes out as (approximately):
$ perl -e 'printf "%e\n",((1/334000)-(1/1211000))'
2.168248e-06
so we're saving approximately 2 microseconds using the + instead of *. Seriously, that's not significant unless you really are running 1000's (or 100's of 1000's) of times. That's pretty much the definition of premature optimisation.
| [reply] [d/l] [select] |
|
|
Just curious Corion...what makes chomp more fragile?
| [reply] |
|
In my experience, any whitespace at the end of what should be a filename is extraneous.
Often, files which list filenames come with Windows line endings (\x0D\x0A) and on Unix, chomp will only remove the \x0A, leaving remaining but erroneous whitespace.
| [reply] [d/l] [select] |
|
|
|