|
Sometimes you already have a filehandle open, and want to make another handle that's a
duplicate of the first one. In the shell, we place an ampersand in front of a file descriptor
number when doing redirections. For example, 2>&1 makes descriptor 2 (that's
STDERR in Perl) be redirected into descriptor 1 (which is usually Perl's STDOUT). The same is
essentially true in Perl: a filename that begins with an ampersand is treated instead as a file
descriptor if a number, or as a filehandle if a string.
open(SAVEOUT, ">&SAVEERR") || die "couldn't dup SAVEERR: $!";
open(MHCONTEXT, "<&4") || die "couldn't dup fd4: $!";
|
|
That means that if a function is expecting a filename, but you don't want to give it a
filename because you already have the file open, you can just pass the filehandle with a leading
ampersand. It's best to use a fully qualified handle though, just in case the function happens
to be in a different package:
somefunction("&main::LOGFILE");
|
|
This way if somefunction() is planning on opening its argument, it can just use the already
opened handle. This differs from passing a handle, because with a handle, you don't open the
file. Here you have something you can pass to open.
If you have one of those tricky, newfangled I/O objects that the C++ folks are raving about,
then this doesn't work because those aren't a proper filehandle in the native Perl sense. You'll
have to use fileno() to pull out the proper descriptor number, assuming you can:
use IO::Socket;
$handle = IO::Socket::INET->new("www.perl.com:80");
$fd = $handle->fileno;
somefunction("&$fd"); # not an indirect function call
|
|
It can be easier (and certainly will be faster) just to use real filehandles though:
use IO::Socket;
local *REMOTE = IO::Socket::INET->new("www.perl.com:80");
die "can't connect" unless defined(fileno(REMOTE));
somefunction("&main::REMOTE");
|
|
If the filehandle or descriptor number is preceded not just with a simple "&"
but rather with a "&=" combination, then Perl will not create a completely new
descriptor opened to the same place using the dup(2) system call. Instead, it will just make
something of an alias to the existing one using the fdopen(3S) library call This is slightly
more parsimonious of systems resources, although this is less a concern these days. Here's an
example of that:
$fd = $ENV{"MHCONTEXTFD"};
open(MHCONTEXT, "<&=$fd") or die "couldn't fdopen $fd: $!";
|
|
If you're using magic <ARGV>, you could even pass in as a command line
argument in @ARGV something like "<&=$MHCONTEXTFD", but we've
never seen anyone actually do this.
Perl is more of a DWIMmer language than something like Java--where DWIM is an acronym for
"do what I mean". But this principle sometimes leads to more hidden magic than one
knows what to do with. In this way, Perl is also filled with dweomer, an obscure word
meaning an enchantment. Sometimes, Perl's DWIMmer is just too much like dweomer for comfort.
If magic open is a bit too magical for you, you don't have to turn to sysopen.
To open a file with arbitrary weird characters in it, it's necessary to protect any leading and
trailing whitespace. Leading whitespace is protected by inserting a "./"
in front of a filename that starts with whitespace. Trailing whitespace is protected by
appending an ASCII NUL byte ("\0") at the end off the string.
$file =~ s#^(\s)#./$1#;
open(FH, "< $file\0") || die "can't open $file: $!";
|
|
This assumes, of course, that your system considers dot the current working directory, slash
the directory separator, and disallows ASCII NULs within a valid filename. Most systems follow
these conventions, including all POSIX systems as well as proprietary Microsoft systems. The
only vaguely popular system that doesn't work this way is the proprietary Macintosh system,
which uses a colon where the rest of us use a slash. Maybe sysopen isn't such a bad
idea after all.
If you want to use <ARGV> processing in a totally boring and non-magical
way, you could do this first:
# "Sam sat on the ground and put his head in his hands.
# 'I wish I had never come here, and I don't want to see
# no more magic,' he said, and fell silent."
for (@ARGV) {
s#^([^./])#./$1#;
$_ .= "\0";
}
while (<>) {
# now process $_
}
|
|
But be warned that users will not appreciate being unable to use "-" to mean
standard input, per the standard convention.
You've probably noticed how Perl's warn and die functions can
produce messages like:
Some warning at scriptname line 29, <FH> line 7.
|
|
That's because you opened a filehandle FH, and had read in seven records from it. But what
was the name of the file, not the handle?
If you aren't running with strict refs, or if you've turn them off temporarily,
then all you have to do is this:
open($path, "< $path") || die "can't open $path: $!";
while (<$path>) {
# whatever
}
|
|
Since you're using the pathname of the file as its handle, you'll get warnings more like
Some warning at scriptname line 29, </etc/motd> line 7.
|
|
Remember how we said that Perl's open took two arguments? That was a passive prevarication.
You see, it can also take just one argument. If and only if the variable is a global variable,
not a lexical, you can pass open just one argument, the filehandle, and it will get
the path from the global scalar variable of the same name.
$FILE = "/etc/motd";
open FILE or die "can't open $FILE: $!";
while (<FILE>) {
# whatever
}
|
|
Why is this here? Someone has to cater to the hysterical porpoises. It's something that's
been in Perl since the very beginning, if not before.
One clever move with STDOUT is to explicitly close it when you're done with the program.
END { close(STDOUT) || die "can't close stdout: $!" }
|
|
If you don't do this, and your program fills up the disk partition due to a command line
redirection, it won't report the error exit with a failure status.
You don't have to accept the STDIN and STDOUT you were given. You are welcome to reopen them
if you'd like.
open(STDIN, "< datafile")
|| die "can't open datafile: $!";
open(STDOUT, "> output")
|| die "can't open output: $!";
|
|
And then these can be read directly or passed on to subprocesses. This makes it look as
though the program were initially invoked with those redirections from the command line.
It's probably more interesting to connect these to pipes. For example:
$pager = $ENV{PAGER} || "(less || more)";
open(STDOUT, "| $pager")
|| die "can't fork a pager: $!";
|
|
This makes it appear as though your program were called with its stdout already piped into
your pager. You can also use this kind of thing in conjunction with an implicit fork to
yourself. You might do this if you would rather handle the post processing in your own program,
just in a different process:
head(100);
while (<>) {
print;
}
sub head {
my $lines = shift || 20;
return unless $pid = open(STDOUT, "|-");
die "cannot fork: $!" unless defined $pid;
while (<STDIN>) {
print;
last if --$lines < 0;
}
exit;
}
|
|
This technique can be applied to repeatedly push as many filters on your output stream as you
wish.
|