http://qs321.pair.com?node_id=11146640

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

Dear wise ones,

A problem that I am having is illustrated by the following simplified code:

print STDERR "This is line 1.\n"; my $var = STDERR; print $var "This is line 2.\n"; my %hash = (key => STDERR); print "The value of the hash variable is $hash{key}.\n"; my $which_stream = $hash{key}; print $which_stream "This is line 3.\n"; #print $hash{key} "This is line 4.\n";

Note that the last instruction is commented out.

When I run the code as shown, I get the following:

This is line 1. This is line 2. The value of the hash variable is STDOUT. This is line 3.

I can confirm by redirection of the output from the above script that it is indeed writing to STDERR, not the default STDOUT, so everything looks good so far.

But if I uncomment that last instruction I get the following:
String found where operator expected at test.pl line 12, near "} "This + is line 4.\n"" (Missing operator before "This is line 4.\n"?) syntax error at test.pl line 12, near "} "This is line 4.\n"" Execution of test.pl aborted due to compilation errors.

So, syntactically it looks as if Perl accepts

print $output_stream $test_message;

but not

print $hash{output_stream} $test_message;

Is there any way, such as through the use of a pragma, to get Perl to accept the last version of the print instruction without converting the hash-based value to a simple scalar value? (This is for This is perl 5, version 34, subversion 0 (v5.34.0) built for x86_64-linux-thread-multi)

The reason why this is a relevant question for me is because I am trying to develop a function in which the output print stream needs to be parameterized, e.g. STDOUT, STDERR, etc., and I am implementing it using a hash as an argument, as illustrated in Problem 10.7 – Passing by Named Parameter in the second edition of the Perl Cookbook by Tom Christiansen. All of my function works fine, including where it uses other values that are defined in the passed hash, but the fact that Perl does not accept syntax where a hash value is used in the position of a print stream specifier token is putting me in a position of having to add extra steps to convert the hash value into a simple scalar, which seems like a kludge.

Thanks much!

-Fireblood
ᏙᎾᏓᎪᎲᎢ

P.S.: No difference when using $hash -> key

Replies are listed 'Best First'.
Re: Syntax error when trying to use a hash value as a file stream specifier (updated)
by AnomalousMonk (Archbishop) on Sep 02, 2022 at 19:15 UTC
    #print $hash{key} "This is line 4.\n";

    Note {} curly braces around $hash{key}:
        print {$hash{key}} "This is line 4.\n";

    Update: From print:

    If you're storing handles in an array or hash, or in general whenever you're using any expression more complex than a bareword handle or a plain, unsubscripted scalar variable to retrieve it, you will have to use a block returning the filehandle value instead, in which case the LIST may not be omitted:

        print { $files[$i] } "stuff\n";
        print { $OK ? *STDOUT : *STDERR } "stuff\n";
    (Update: The documentation excerpted here refers to Indirect Object syntax. Please see kcott's ++reply and the replies of others in this thread for the way to avoid this syntax and for the wisdom of doing so.)


    Give a man a fish:  <%-{-{-{-<

Re: Syntax error when trying to use a hash value as a file stream specifier
by BillKSmith (Monsignor) on Sep 02, 2022 at 20:36 UTC
    The book "Perl Best Practices" (ISBN 0596001738) recommends that you always use braces around the file handle in a print statements to make it clear that you did not forget a comma. This practice would eliminate your problem as well.

    Did you consider redirecting your default output with select rather than passing it as an argument?

    UPDATE: I believe that the OP is passing a file-handle to a subroutine using the method that his reference (Cookbook recipe 10.7) calls 'Named Parameters'. Using this method, the file-handle is stored as a value in a hash. He as asking for the syntax to use that value in a print statement. AnomalousMonk has provided the answer. My suggestion to use select to pass the file-handle to the subroutine would work, but I no longer recommend it.

    use strict; use warnings; myfunction1( handle => \*STDERR); sub myfunction1{ my %hash = ( handle => \*STDOUT, @_ ); print "something will happen\n"; print {$hash{handle}} "anything from myfunction1\n"; # ^ ^ # braces added here }
    Bill
      Did you consider redirecting your default output with select rather than passing it as an argument?

      The handle is not passed as an argument, if it was, it would be followed by a comma. It is actually indirect object syntax (method $object @arguments in contrast to $object->method(@arguments)), see perlobj.

      Using select is a global operation, it switches the default handle for all write, print, and (not yet documented in select) say <Update>without an explicit handle</Update> from STDOUT (or whatever was previously selected) to the handle passed to select. This has consequences that may lead to unwanted features a.k.a. bugs:

      package Some::Third::Party::Module; # ... sub doSomething { say "This will take some time"; sleep 10; say "Done"; } # ...
      #!/usr/bin/perl # ... use Some::Third::Party::Module qw( doSomething ); # ... open my $h,'>:raw','output.bin'; my $oldSelected=select $h; print "\x00\x42\xAF\x99"; doSomething(); print "\x33\xFF\x81\x73"; select $oldSelected; close $h;

      Guess why output.bin is longer than expected and does not work as expected.

      Using the usual indirect object notation fixes the problem:

      open my $h,'>:raw','output.bin'; print $h "\x00\x42\xAF\x99"; doSomething(); print $h "\x33\xFF\x81\x73"; close $h;

      Of course, if you like the mental pain and want to get hurt by the future maintainer, you could also select handles as needed. Welcome back to the 1960s, when this was the only way to handle files in MUMPS:

      open my $h,'>:raw','output.bin'; my $oldSelected=select $h; print "\x00\x42\xAF\x99"; select $oldSelect; doSomething(); select $h; print "\x33\xFF\x81\x73"; select $oldSelected; close $h;

      I would not use select to globally switch file handles, not even for a short piece of code. It just causes way too much trouble.

      Alexander

      --
      Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
        Good point! The function select acts as if it controls an anonymous global variable. That 'variable' has all the advantages and disadvantages of any global variable except that we cannot examine it. In your example, we all expect say to output to stdout. It is easy to forget that it may not be true.

        Until the documentation for say is corrected, the victim of this bug could certainly claim that the bug is in perl, not his code.

        Bill
Re: Syntax error when trying to use a hash value as a file stream specifier
by kcott (Archbishop) on Sep 02, 2022 at 21:08 UTC

    G'day Fireblood,

    In the examples below, I've used a common alias of mine which picks up many problems:

    $ alias perle alias perle='perl -Mstrict -Mwarnings -Mautodie=:all -MCarp::Always -E +'

    Probably the first thing to note is that filehandles are globrefs. Here's an example (along with a hashref for comparison).

    $ perle 'open my $fh, ">", "not_a_file"; say "$fh"; unlink "not_a_file +";' GLOB(0x8000c42e0) $ perle 'my $hashref = {}; say "$hashref";' HASH(0x800003bc8)

    Had you used the strict pragma, you would have seen:

    Bareword "STDERR" not allowed while "strict subs" in use ...

    Always start your code with the following:

    use strict; use warnings;

    or code that gives you one or both of those (e.g. use v5.12 gives you strict; use v5.36 gives you strict and warnings; various modules have similar effects):

    The next question might be, what should you use instead of STDERR? I'd argue that \*STDERR is strictly correct as it's a globref; however, *STDERR will also work.

    $ perle 'my $x = \*STDERR; say "$x";' GLOB(0x80008fcd0) $ perle 'my $x = *STDERR; say "$x";' *main::STDERR

    That finally brings us to how to use the hash value without an intermediate scalar value. You have two main options (perhaps others will suggest additional ones).

    • As already mentioned by ++AnomalousMonk, continue to use "Indirect Object Syntax" and wrap the hash value in braces:
      $ perle 'my %fh = (err => \*STDERR); print {$fh{err}} "errmsg\n";' errmsg $ perle 'my %fh = (err => *STDERR); print {$fh{err}} "errmsg\n";' errmsg
    • Use the hash value with the filehandle, and invoke print, say, and so on, as methods:
      $ perle 'my %fh = (err => \*STDERR); $fh{err}->print("errmsg\n");' errmsg $ perle 'my %fh = (err => *STDERR); $fh{err}->print("errmsg\n");' errmsg

    Side note: All of the above examples were run using Perl v5.36. This version disables the indirect feature by default (see "perl5360delta: use v5.36"). Obviously, the filehandle case is still allowed; however, if you do use that syntax elsewhere (e.g. my $obj = new Class ...), you may want to consider getting out of the habit of doing so.

    — Ken

Re: Syntax error when trying to use a hash value as a file stream specifier
by LanX (Saint) on Sep 03, 2022 at 11:50 UTC
    classic filehandles° and indirect object syntax are a PITA constantly causing trouble.

    may I suggest you use the approach to call print as a direct method?

    > perl -de0 ... DB<108> open my $x, ">", \$out; $h{k}=$x DB<109> $h{k}->print("bla") DB<110> p $out bla DB<111> open FH, ">", \$out2; $h{k2}=FH DB<112> $h{k2}->print("bla") DB<113> p $out2 bla DB<114>

    NB: I directed the output to $out vars only for demo purpose, it will work with all destinations.

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

    °) bareword FH, glob *FH, glob-ref \*FH, ...

      Sorry, this

      > $h{k2}=FH

      will only work in no strict scenarios, here corrected versions

      use v5.12; use warnings; use Data::Dump; my %h; open my $fh, ">>", \ my $out; $h{k}=$fh; $h{k}->print("bla"); dd $out; open FH2, ">>", \ my $out2; $h{k2}=*FH2; $h{k2}->print("bla2"); dd $out2; open FH3, ">>", \ my $out3; $h{k3}=\*FH3; $h{k3}->print("bla3"); dd $out3; # open FH4, ">>", \ my $out4; # $h{k4}=FH4; # $h{k4}->print("bla4"); # dd $out4; # Bareword "FH4" not allowed while "strict subs" in use at c:/tmp/pm/f +h.pl line 27. # Execution of c:/tmp/pm/fh.pl aborted due to compilation errors.

      "bla" "bla2" "bla3"

      FWIW: I prefer the 3rd $h{k3}=\*FH3 over the 2nd variant

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery