views:

454

answers:

7

I am new to Perl. Know a little bit of C though. I came across this snippet in one of our classroom notes :

$STUFF="c:/scripts/stuff.txt";

open STUFF or die "Cannot open $STUFF for read :$!";

print "Line $. is : $_" while (<STUFF>);

Why is the while after the print statement? What does it do?

+9  A: 

It's the same as

while (<STUFF>) {
    print "Line $. is : $_";
}

It's written the way it is because it's simpler and more compact. This is, in general, Perl's (in)famous "statement modifier" form for conditionals and loops.

chaos
I'd disagree that it is "simpler" (if it were, this question would not exist!)
dbr
It is simpler. What it isn't is intuitively obvious to every person for every background. When you're judging whether two things are simpler or more complex relative to each other, I really think you have to work with the assumption that the viewer knows what both mean, rather than saying "oh, this is more complex because I know C and not Perl".
chaos
It's more compact, not necessarily simpler. I find it about as simple, but I've known Perl for a while now.
Brad Gilbert
What I really mean is that it's simpler in an Occam's Razor sort of sense: it requires fewer entities. Enclosure-form conditionals and loops in Perl require a BLOCK, and therefore its enclosure characters (and its indentation, if one has any class at all); statement-modifier form does not.
chaos
A: 

Same as a

while (<STUFF>) { print "Line $. is : $_"; }
Silfverstrom
+4  A: 

The following snippets are exactly equivalent, just different ways of writing the same thing:

print "Line $. is : $_" while (<STUFF>);

while (<STUFF>) {
    print "Line $. is : $_";
}

What this does is each iteration through the loop, Perl reads one line of text from the STUFF file and puts it in the special variable $_ (this is what the angle brackets do). Then the body of the loop prints lines like:

Line 1 is : test
Line 2 is : file

The special variable $. is the line number of the last line read from a file, and $_ is the contents of that line as set by the angle bracket operator.

Greg Hewgill
+3  A: 

Placing the while after the print, make the line read almost like normal English. It also puts emphasis on the print instead of the while. And you don't need the curly brackets: { ... }

Can also be used with if and unless, e.g.

print "Debug: foobar=$foobar\n" if $DEBUG;

print "Load file...\n" unless $QUIET;
slu
Thanks for letting me know abt 'if' and 'unless' before i posted that too to stackoverflow :-)
+1  A: 

Note that the while statement can only follow your loop body if it's a one-liner. If your loop body runs over several lines, then your while has to precede it.

Zaid
this is more like C; thanks :-)
PERL borrows its syntax from several different languages, including English.Allowing the conditional statement to follow a one-line block makes it read more like English.You may also want to look at the `unless` and `until` keywords, which act like negated versions of `if` and `while`. This is something that C doesn't have as far as I know. Using it does sometimes improve the readability of your code.
Zaid
+5  A: 

The other answers have explained the statement modifier form of the while loop. However, there's a lot of other magic going on here. In particular, the script relies on three of Perl's special variables. Two of these ($_ and $!) are very common; the other ($.) is reasonably common. But they're all worth knowing.

When you run while <$fh> on an opened filehandle, Perl automagically runs through the file, line by line, until it hits EOF. Within each loop, the current line is set to $_ without you doing anything. So these two are the same:

while (<$fh>) { # something }
while (defined($_ = <$fh>)) { # something }

See perldoc perlop, the section on I/O operators. (Some people find this too magical, so they use while (my $line = <$fh>) instead. This gives you $line for each line rather than $_, which is a clearer variable name, but it requires more typing. To each his or her own.)

$! holds the value of a system error (if one is set). See perldoc perlvar, the section on $OS_ERROR, for more on how and when to use this.

$. holds a line number. See perldoc perlvar, the section on $NR. This variable can be surprisingly tricky. It won't necessarily have the line number of the file you are currently reading. An example:

#!/usr/bin/env perl
use strict;
use warnings;

while (<>) {
    print "$ARGV: $.\n";
}

If you save this as lines and run it as perl lines file1 file2 file3, then Perl will count lines straight through file1, file2 and file3. You can see that Perl knows what file it's reading from (it's in $ARGV; the filenames will be correct), but it doesn't reset line numbering automatically for you at the end of each file. I mention this since I was bit by this behavior more than once until I finally got it through my (thick) skull. You can reset the numbering to track individual files this way:

#!/usr/bin/env perl
use strict;
use warnings;

while (<>) {
    print "$ARGV: $.\n";
}
continue {
    close ARGV if eof;
}

You should also check out the strict and warnings pragmas and take a look at the newer, three-argument form of open. I just noticed that you are "unknown (google)", which means you are likely never to return. I guess I got my typing practice for the day, at least.

Telemachus
Good lord! And i thought C was the most complicated language. Perl does a lot of hidden things too. As far as unknown(google) is concerned, i am a student right now; too many teachers here for me to expose myself :-), maybe later i will try to be myself. I have given you your +1 BTW :-)
I wasn't worried about the rep (though thanks). It just would have been a shame to type all that and _not_ get it read. Glad you saw it. I have minimal experience with C, so I can't compare. But my sense is that Perl's complications mostly make your life easier. Perl will very, very frequently automatically do what you want. It's a good thing.
Telemachus
+3  A: 

I've taken the liberty of rewriting your snippet as I would.

Below my suggested code is a rogues gallery of less than optimal methods you might see in the wild.

use strict;
use warnings;

my $stuff_path = 'c:/scripts/stuff.txt';

open (my $stuff, '<', $stuff_path)
    or die "Cannot open'$stuff_path' for read : $!\n";

# My preferred method to loop over a file line by line:
# while loop with explicit variable
while( my $line = <$stuff> ) {
    print "Line $. is : $line\n";
}

Here are other methods you might see. Each one could be substituted for the while loop in my example above.

# while statement modifier
# - OK, but less clear than explicit code above.
print "Line $. is : $_" while <$stuff>;

# while loop
# - OK, but if I am operating on $_ explicitly, I prefer to use an explicit variable.
while( <$stuff> ) {
   print "Line $. is : $_";
}

# for statement modifier 
# - inefficient
# - loads whole file into memory
print "Line $. is : $_" for <$stuff>;

# for loop - inefficient
# - loads whole file into memory;
for( <$stuff> ) {
    print "Line $. is : $_\n";
}

# for loop with explicit variable
# - inefficient
# - loads whole file into memory;
for my $line ( <$stuff> ) {
    print "Line $. is : $line\n";
}

# Exotica -

# map and print
# - inefficient
# - loads whole file into memory
# - stores complete output in memory
print map "Line $. is : $_\n", <$stuff>;

# Using readline rather than <>
# - Alright, but overly verbose
while( defined (my $line = readline($stuff) ) {
    print "Line $. is : $line\n";    
}

# Using IO::Handle methods on a lexical filehandle
# - Alright, but overly verbose
use IO::Handle;   
while( defined (my $line = $stuff->readline) ) {
    print "Line $. is : $line\n";    
}
daotoad