|
Perl's open function was designed to mimic the way command-line redirection in
the shell works. Here are some basic examples from the shell:
$ myprogram file1 file2 file3
$ myprogram < inputfile
$ myprogram > outputfile
$ myprogram >> outputfile
$ myprogram | otherprogram
$ otherprogram | myprogram
|
|
And here are some more advanced examples:
$ otherprogram | myprogram f1 - f2
$ otherprogram 2>&1 | myprogram -
$ myprogram <&3
$ myprogram >&4
|
|
Programmers accustomed to constructs like those above can take comfort in learning that Perl
directly supports these familiar constructs using virtually the same syntax as the shell.
The open function takes two arguments: the first is a filehandle, and the second
is a single string comprising both what to open and how to open it. open returns
true when it works, and when it fails, returns a false value and sets the special variable $! to
reflect the system error. If the filehandle was previously opened, it will be implicitly closed
first.
For example:
open(INFO, "datafile") || die("can't open datafile: $!");
open(INFO, "< datafile") || die("can't open datafile: $!");
open(RESULTS,"> runstats") || die("can't open runstats: $!");
open(LOG, ">> logfile ") || die("can't open logfile: $!");
|
|
If you prefer the low-punctuation version, you could write that this way:
open INFO, "< datafile" or die "can't open datafile: $!";
open RESULTS,"> runstats" or die "can't open runstats: $!";
open LOG, ">> logfile " or die "can't open logfile: $!";
|
|
A few things to notice. First, the leading less-than is optional. If omitted, Perl assumes
that you want to open the file for reading.
The other important thing to notice is that, just as in the shell, any white space before or
after the filename is ignored. This is good, because you wouldn't want these to do different
things:
open INFO, "<datafile"
open INFO, "< datafile"
open INFO, "< datafile"
|
|
Ignoring surround whitespace also helps for when you read a filename in from a different
file, and forget to trim it before opening:
$filename = <INFO>; # oops, \n still there
open(EXTRA, "< $filename") || die "can't open $filename: $!";
|
|
This is not a bug, but a feature. Because open mimics the shell in its style of
using redirection arrows to specify how to open the file, it also does so with respect to extra
white space around the filename itself as well. For accessing files with naughty names, see "Dispelling the Dweomer".
In C, when you want to open a file using the standard I/O library, you use the fopen
function, but when opening a pipe, you use the popen function. But in the shell,
you just use a different redirection character. That's also the case for Perl. The open
call remains the same--just its argument differs.
If the leading character is a pipe symbol, open starts up a new command and open
a write-only filehandle leading into that command. This lets you write into that handle and have
what you write show up on that command's standard input. For example:
open(PRINTER, "| lpr -Plp1") || die "can't run lpr: $!";
print PRINTER "stuff\n";
close(PRINTER) || die "can't close lpr: $!";
|
|
If the trailing character is a pipe, you start up a new command and open a read-only
filehandle leading out of that command. This lets whatever that command writes to its standard
output show up on your handle for reading. For example:
open(NET, "netstat -i -n |") || die "can't fun netstat: $!";
while (<NET>) { } # do something with input
close(NET) || die "can't close netstat: $!";
|
|
What happens if you try to open a pipe to or from a non-existent command? If possible, Perl
will detect the failure and set $! as usual. But if the command contains special
shell characters, such as > or *, called 'metacharacters', Perl
does not execute the command directly. Instead, Perl runs the shell, which then tries to run the
command. This means that it's the shell that gets the error indication. In such a case, the open
call will only indicate failure if Perl can't even run the shell. See perlfaq8/"How
can I capture STDERR from an external command?" to see how to cope with this. There's
also an explanation in perlipc.
If you would like to open a bidirectional pipe, the IPC::Open2 library will handle this for
you. Check out perlipc/"Bidirectional
Communication with Another Process"
Again following the lead of the standard shell utilities, Perl's open function
treats a file whose name is a single minus, "-", in a special way. If you open minus
for reading, it really means to access the standard input. If you open minus for writing, it
really means to access the standard output.
If minus can be used as the default input or default output, what happens if you open a pipe
into or out of minus? What's the default command it would run? The same script as you're
currently running! This is actually a stealth fork hidden inside an open
call. See perlipc/"Safe
Pipe Opens" for details.
It is possible to specify both read and write access. All you do is add a "+"
symbol in front of the redirection. But as in the shell, using a less-than on a file never
creates a new file; it only opens an existing one. On the other hand, using a greater-than
always clobbers (truncates to zero length) an existing file, or creates a brand-new one if there
isn't an old one. Adding a "+" for read-write doesn't affect whether it only works on
existing files or always clobbers existing ones.
open(WTMP, "+< /usr/adm/wtmp")
|| die "can't open /usr/adm/wtmp: $!";
open(SCREEN, "+> /tmp/lkscreen")
|| die "can't open /tmp/lkscreen: $!";
open(LOGFILE, "+>> /tmp/applog"
|| die "can't open /tmp/applog: $!";
|
|
The first one won't create a new file, and the second one will always clobber an old one. The
third one will create a new file if necessary and not clobber an old one, and it will allow you
to read at any point in the file, but all writes will always go to the end. In short, the first
case is substantially more common than the second and third cases, which are almost always
wrong. (If you know C, the plus in Perl's open is historically derived from the one
in C's fopen(3S), which it ultimately calls.)
In fact, when it comes to updating a file, unless you're working on a binary file as in the
WTMP case above, you probably don't want to use this approach for updating. Instead, Perl's -i
flag comes to the rescue. The following command takes all the C, C++, or yacc source or header
files and changes all their foo's to bar's, leaving the old version in the original file name
with a ".orig" tacked on the end:
$ perl -i.orig -pe 's/\bfoo\b/bar/g' *.[Cchy]
|
|
This is a short cut for some renaming games that are really the best way to update textfiles.
See the second question in perlfaq5
for more details.
One of the most common uses for open is one you never even notice. When you
process the ARGV filehandle using <ARGV>, Perl actually does an implicit open
on each file in @ARGV. Thus a program called like this:
$ myprogram file1 file2 file3
|
|
Can have all its files opened and processed one at a time using a construct no more complex
than:
while (<>) {
# do something with $_
}
|
|
If @ARGV is empty when the loop first begins, Perl pretends you've opened up minus, that is,
the standard input. In fact, $ARGV, the currently open file during <ARGV>
processing, is even set to "-" in these circumstances.
You are welcome to pre-process your @ARGV before starting the loop to make sure it's to your
liking. One reason to do this might be to remove command options beginning with a minus. While
you can always roll the simple ones by hand, the Getopts modules are good for this.
use Getopt::Std;
# -v, -D, -o ARG, sets $opt_v, $opt_D, $opt_o
getopts("vDo:");
# -v, -D, -o ARG, sets $args{v}, $args{D}, $args{o}
getopts("vDo:", \%args);
|
|
Or the standard Getopt::Long module to permit named arguments:
use Getopt::Long;
GetOptions( "verbose" => \$verbose, # --verbose
"Debug" => \$debug, # --Debug
"output=s" => \$output );
# --output=somestring or --output somestring
|
|
Another reason for preprocessing arguments is to make an empty argument list default to all
files:
@ARGV = glob("*") unless @ARGV;
|
|
You could even filter out all but plain, text files. This is a bit silent, of course, and you
might prefer to mention them on the way.
@ARGV = grep { -f && -T } @ARGV;
|
|
If you're using the -n or -p command-line options, you should put changes to @ARGV
in a BEGIN{} block.
Remember that a normal open has special properties, in that it might call
fopen(3S) or it might called popen(3S), depending on what its argument looks like; that's why
it's sometimes called "magic open". Here's an example:
$pwdinfo = `domainname` =~ /^(\(none\))?$/
? '< /etc/passwd'
: 'ypcat passwd |';
open(PWD, $pwdinfo)
or die "can't open $pwdinfo: $!";
|
|
This sort of thing also comes into play in filter processing. Because <ARGV>
processing employs the normal, shell-style Perl open, it respects all the special
things we've already seen:
$ myprogram f1 "cmd1|" - f2 "cmd2|" f3 < tmpfile
|
|
That program will read from the file f1, the process cmd1, standard input (tmpfile
in this case), the f2 file, the cmd2 command, and finally the f3 file.
Yes, this also means that if you have a file named "-" (and so on) in your
directory, that they won't be processed as literal files by open. You'll need to
pass them as "./-" much as you would for the rm program. Or you could use sysopen
as described below.
One of the more interesting applications is to change files of a certain name into pipes. For
example, to autoprocess gzipped or compressed files by decompressing them with gzip:
@ARGV = map { /^\.(gz|Z)$/ ? "gzip -dc $_ |" : $_ } @ARGV;
|
|
Or, if you have the GET program installed from LWP, you can fetch URLs before
processing them:
@ARGV = map { m#^\w+://# ? "GET $_ |" : $_ } @ARGV;
|
|
It's not for nothing that this is called magic <ARGV>. Pretty nifty, eh?
|
|