jktstance has asked for the wisdom of the Perl Monks concerning the following question:

Quick one here. I've tried searching around, but I can't make heads or tails.

If I open a file for reading, open FP, "<", "test.txt"; then the following should loop over each line and stop when <FP> is false:

while (<FP>) { ... }
However, what if the file test.txt has a single line in it that is simply "0"? How come the while loop continues? Shouldn't it stop because <FP> returns 0, which is false? My only guess as to why it continues is because <FP> actually returns 0 and a newline character.

From this wonderful resource, it says that the while loop only stops on undef. I can't seem to find official documentation as to why the conditional changes from a Boolean to defined() in the presence of the diamond operator.

Replies are listed 'Best First'.
Re: while (<FP>) conditionals
by davido (Cardinal) on Feb 04, 2014 at 22:25 UTC

    This:

    while( <> ) { ...

    ...has special meaning in Perl. It's semantically equal to:

    while( defined( $_ = <> ) ) { ...

    So your loop doesn't terminate upon reading a false value. It terminates when <> returns undef, which can happen when you reach the end of the file.

    This is documented in perlop. It really is a special case. The implicit "defined" is just a "Do What I Mean" convenience.


    Dave

Re: while (<FP>) conditionals
by Athanasius (Archbishop) on Feb 05, 2014 at 04:48 UTC
    My only guess as to why it continues is because <FP> actually returns 0 and a newline character.

    It does, and a string containing “0\n” is true, not false:

    14:26 >perl -we "my $s = qq[0\n]; printf qq[%s\n], ($s ? 'true' : 'fal +se');" true 14:31 >

    To see the special behaviour described by ++davido, you can set the $/ variable (the input record separator) to read 1 character at a time, and then try a C-style for-loop (which does not have the special behaviour):

    #! perl use strict; use warnings; local $/ = \1; for ($_ = <DATA>; $_; $_ = <DATA>) { print "<$_>\n"; } __DATA__ 1 0 42

    — the loop terminates on the 0. To get the desired behaviour, change it to:

    for ($_ = <DATA>; defined $_; $_ = <DATA>)

    and a little experimentation will show you that this is now equivalent to the special forms:

    print "<$_>\n" for <DATA>;

    and

    print "<$_>\n" while <DATA>;

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

      It does, thank you. I tried running your first test:
      perl -we "my $s = qq[0\n]; printf qq[%s\n], ($s ? 'true' : 'fal +se');
      All I ever got were the following errors:
      syntax error at -e line 1, near "my =" Search pattern not terminated or ternary operator parsed as search pat +tern at -e line 1.
      However, when I put it into a script and ran it, it worked fine. Also, your second test does not work for me:
      #! perl use strict; use warnings; local $/ = \1; for ($_ = <DATA>; $_; $_ = <DATA>) { print "<$_>\n"; } __DATA__ 1 0 42
      When I run it, I get the following:
      <1> < >
      But at this point, my question is answered. Now we're just toying around!
        When typing code on the command line, you often have to escape some characters depending on your system.
        For example, the error message shows that: my $s = got munged by your shell into my  = before it was passed to perl.
        Also, your second test does not work for me ... When I run it, I get the following:

        But that’s exactly what you should get! local $/ = \1; makes the diamond operator read one character at a time. The first character is 1, which is printed. The second character is a newline, which is also printed (I put the angle brackets in to show that a new line has been printed). The third character is 0, and because the C-style for-loop is not magic, this is “false” and the loop terminates without printing anything more.

        Hope that helps,

        Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,