5

Does STDOUT have a "type"?

printf STDERR ("STDOUT = %s\n", STDOUT);
printf STDERR ("\*STDOUT = %s\n", *STDOUT);
printf STDERR ("\\\*STDOUT = %s\n", \*STDOUT);

Produces:

STDOUT = STDOUT
*STDOUT = *main::STDOUT
\*STDOUT = GLOB(0x600078848)

I understand the *main::STDOUT and GLOB(0x600078848) entries. The "bareword" one leaves me curious.

I'm asking because I want to pass a file handle-like argument to a method call. In 'C', I'd use a file descriptor or a File *. I want it to default to STDOUT. What I've done is:

$OUT_FILE_HANDLE = \*STDOUT;
if(@ARGV > 0 ) {
    open($OUT_FILE_HANDLE, ">", "$ARGV[0]") or die $!;
}

It works, but I don't know exactly what I've done. Have I botched up STDOUT? I suspect I have "ruined" (overwritten) STDOUT, which is NOT what I want.

Please pardon the compound question; they seemed related.

Erik Bennett
  • 1,049
  • 6
  • 15
  • 2
    The `STDOUT` in `printf STDERR ("STDOUT = %s\n", STDOUT);` is an example of the "poetry optimization" compatibility misfeature -- the `STDOUT` bareword is treated as a literal string, the same as `"STDOUT"`. That's an error with `use strict`, which you should **always** use. As to your claim of understanding typeglobs and handles -- great for you! I always have to think of how they're implemented in the interpreter in order to make sense of the mess. –  Jul 23 '19 at 03:12

3 Answers3

5

Create a lexical filehandle to be a copy of STDOUT and manipulate that as needed

sub manip_fh { 
    my ($fh) = @_;
    say $fh "hi STDOUT";                       # goes to STDOUT
    open my $fh, '>', 'a_file.txt' or die $!;  # now it's to a file
    say $fh "hello file";
}

open my $fh, '>&', STDOUT;  # via dup(2)

manip_fh($fh);

say "hi";  # still goes where STDOUT went before being dup-ed (terminal)

This new, independent, filehandle can then be reopened to another resource without affecting STDOUT. See open.

The $OUT_FILE_HANDLE = \*STDOUT; from the question creates an alias and so the STDOUT does indeed get changed when the "new" one changes. You can see that by printing the typeglob

our $NEW = \*STDOUT;  # "our" only for checks here, otherwise better "my"
say *{$main::NEW};    #--> *main::STDOUT

or by printing the IO slot from the symbol table for both

say for *{$main::NEW}{IO}, *{$main::{STDOUT}}{IO};

and seeing (that the object stringifies to) the same (eg IO::File=IO(0x1a8ca50)).

When it's duped using open with mode >& as in the first code snippet -- and as a global our instead of my, for these checks to be possible -- it prints *main::NEW, and its IO::File object is not the same as for STDOUT.

Make it a global our only so that it is in the symbol table for these checks (local lexicals declared with my aren't), but not otherwise; it is much much better having a my.

zdim
  • 64,580
  • 5
  • 52
  • 81
4

From perlvar:

Perl identifiers that begin with digits or punctuation characters are exempt from the effects of the package declaration and are always forced to be in package main; they are also exempt from strict 'vars' errors. A few other names are also exempt in these ways: [...] STDOUT

So, STDOUT is a global variable containing a pre-opened file handle.

From perlfunc:

If FILEHANDLE is an undefined scalar variable (or array or hash element), a new filehandle is autovivified, meaning that the variable is assigned a reference to a newly allocated anonymous filehandle. Otherwise if FILEHANDLE is an expression, its value is the real filehandle.

Your $OUT_FILE_HANDLE is not undefined, so it is its value, STDOUT, that is being opened. AFAIK, if you open an already open handle, it is implicitly closed first.

There are several ways to do what you want. The first is obvious from the above quote — do not define $OUT_FILE_HANDLE before the open:

if (@ARGV > 0 ) {
  open($OUT_FILE_HANDLE, ">", "$ARGV[0]") or die $!;
} else {
  $OUT_FILE_HANDLE = \*STDOUT;
}
# do stuff to $OUT_FILE_HANDLE

Another is to use select, so you don't need to pass a file handle:

if (@ARGV > 0 ) {
  open($OUT_FILE_HANDLE, ">", "$ARGV[0]") or die $!;
  select $OUT_FILE_HANDLE;
}
# do stuff (without specifying a file handle)
select STDOUT;
Amadan
  • 191,408
  • 23
  • 240
  • 301
  • `select` is probably the more perl/lisp thing to do. I was also thinking of a dynamically scoped `STDOUT`. This may be how select is implemented. Thanks. – Erik Bennett Jul 23 '19 at 03:03
2

This part of your question wasn't answered:

The "bareword" one leaves me curious.

An identifier with no other meaning is a string literal that produces itself.[1] For example, foo is the same as 'foo'.

$ perl -e'my $x = foo; print "$x\n";'
foo

This is error-prone, so we use use strict qw( subs ); to prevent this.

$ perl -e'use strict; my $x = foo; print "$x\n";'
Bareword "foo" not allowed while "strict subs" in use at -e line 1.
Execution of -e aborted due to compilation errors.

  1. See this for other meanings Perl could assign.
ikegami
  • 367,544
  • 15
  • 269
  • 518
  • I thought it sometime was interpreted as a `sub`? I don't know, since I always `use strict`. I had to take it out for this example, just to see what would happen. – Erik Bennett Jul 24 '19 at 11:09
  • It's interpreted as a sub call if a sub with that name has previously been declared. That's one of the "other meanings" to which I referred. – ikegami Jul 24 '19 at 13:19
  • I've got another question, but I don't know how to phrase it. Is there a programmatic way to see/know how the compiler is going to interpret a bare word? In my program above, I tried to print STDOUT, and it hit #8, and not #7. I think. – Erik Bennett Jul 26 '19 at 15:48
  • 1
    You can use `-MO=Concise` or `-MO=Concise,-exec` to see how the program is compiled. /// That list is original content created for this answer. There could be a mistake, but I'm pretty confident due to extensive testing. (If anyone thinks there's a situation in which they think Perl interprets an identifier differently than this chart, I would very much like to hear about it so I can fix the chart). /// If you're referring to `printf STDERR ("STDOUT = %s\n", STDOUT);`, `printf`: #5, `STDERR`: #7, `STDOUT`: #8 – ikegami Jul 26 '19 at 16:01
  • 1
    The two examples of **4** are completely different, and only one of them will override **5**. Compare `perl -le 'use constant length => 7; print length'` (fails to override `length`) with `perl -le 'BEGIN { package P; *{main::length} = sub(){ 17 } } print length'` (handcrafted Exporter, overrides `length`, constant folding just like `use constant`). –  Jul 27 '19 at 21:49
  • @mosvy, Thanks. Moved `use constant` from #4 to #6 – ikegami Jul 27 '19 at 21:51