Using Backquotes to Capture Output

14.4 Using Backquotes to Capture Output

With both system and exec, the output of the launched command ends up wherever Perl's standard output is going. Sometimes, it's interesting to capture that output as a string value to perform further processing. And that's done simply by creating a string using backquotes instead of single or double quotes:

my $now = `date`;  # grab the output of date
print "The time is now $now"; # newline already present

Normally, this date command spits out a string approximately 30 characters long to its standard output, giving the current date and time followed by a newline. When we've placed date between backquotes, Perl executes the date command, arranging for its standard output to be captured as a string value, and in this case assigned to the $now variable.

This is very similar to the Unix shell's meaning for backquotes. However, the shell also performs the additional job of ripping off the final end-of-line to make it easier to use the value as part of other things. Perl is honest; it gives the real output. To get the same result in Perl, we can simply add an additional chomp operation on the result:

chomp(my $no_newline_now = `date`);
print "A moment ago, it was $no_newline_now, I think.\n";

The value beween backquotes is just like the single-argument form of system,[13] and is interpreted as a double-quoted string, meaning that backslash-escapes and variables are expanded appropriately.[14] For example, to fetch the Perl documentation on a list of Perl functions, we might invoke the perldoc command repeatedly, each time with a different argument:

[13] That is, it's also always interpreted by the One True Shell (/bin/sh) or alternative, as with system.

[14] So, if you want to pass a real backslash to the shell, you'll need to use two. If you need to pass two (which happens frequently on Windows systems), you'll need to use four.

my @functions = qw{ int rand sleep length hex eof not exit sqrt umask };
my %about;
 
foreach (@functions) {
  $about{$_} = `perldoc -t -f $_`;
}

Note that $_ will be a different value for each invocation, letting us grab the output of a different command varying only in one of its parameters. Also note that if you haven't seen some of these functions yet, it might be useful to look them up in the documentation to see what they do!

There's no easy equivalent of single quotes for backquotes[15] ; variable references and backslash items are always expanded. Also, there's no easy equivalent of the multiple-argument version of system (where a shell is never involved). If the command inside the backquotes is complex enough, a Unix Bourne Shell (or whatever your system uses instead) is invoked to interpret the command automatically.

[15] For a couple of harder ways, you can place your string inside qx'...'delimiters, or you can put it all in a variable using a single-quoted string, then interpolate that string into a backquoted string, since the interpolation will be only one level.

At the risk of actually introducing the behavior by demonstrating how not to do it, we'd also like to suggest that you avoid using backquotes in a place where the value isn't being captured.[16] For example:

[16] This is called a "void" context.

print "Starting the frobnitzigator:\n";
`frobnitz -enable`; # please don't do this!
print "Done!\n";

The problem is that Perl has to work a bit harder to capture the output of this command, even when you're just throwing it away, and then you also lose the option to use multiple arguments to system to precisely control the argument list. So from both a security standpoint and an efficiency viewpoint, just use system instead, please.

Standard error of a backquoted command is inherited from Perl's current standard error output. If the command spits out error messages to standard error, you'll probably see them on the terminal, which could be confusing to the user who hasn't personally invoked the frobnitz command. If you want to capture error messages with standard output, you can use the shell's normal "merge standard error to the current standard output," which is spelled 2>&1 in the normal Unix shell:

my $output_with_errors = `frobnitz -enable 2>&1`;

Note that this will make the standard error output intermingled with the standard output, much as it appears on the terminal (although possibly in a slightly different sequence because of buffering). If you need the output and the error output separated, there are many harder-to-type solutions.[17]

[17] Such as IPC::Open3 in the standard Perl library, or writing your own forking code, as we will see later.

Similarly, standard input is inherited from Perl's current standard input. Most commands we typically use with backquotes do not read standard input, so that's rarely a problem. However, let's say the date command asked which time zone (as we imagined earlier). That'll be a problem, because the prompt for "which time zone" will be sent to standard output, which is being captured as part of the value, and then the date command will start trying to read from standard input. But since the user has never seen the prompt, he or she doesn't know to be typing anything! Pretty soon, the user calls you up and tells you that your program is stuck.

So, stay away from commands that read standard input. If you're not sure whether something reads from standard input, then add a redirection from /dev/null for input, like this:

my $result = `some_questionable_command arg arg argh </dev/null`;

Then the child shell will redirect input from /dev/null, and the grandchild questionable command will at worst try to read and immediately get an end of file.

14.4.1 Using Backquotes in a List Context

If the output from a command has multiple lines, the scalar use of backquotes returns it as a single long string containing newline characters. However, using the same backquoted string in a list context yields a list containing one line of output per element.

For example, the Unix who command normally spits out a line of text for each current login on the system as follows:

merlyn   tty/42  Dec 7  19:41
rootbeer  console  Dec 2  14:15
rootbeer  tty/12  Dec 6  23:00

The left column is the username, the middle column is the tty name (that is, the name of the user's connection to the machine), and the rest of the line is the date and time of login (and possibly remote login information, but not in this example). In a scalar context, we get all that at once, which we would then need to split up:

my $who_text = `who`;

But in a list context, we automatically get the data broken up by lines:

my @who_lines = `who`;

We'll have a number of separate elements in @who_lines, each one terminated by a newline. Of course, adding a chomp around the outside of that will rip off those newlines, but let's go a different direction. If we put that as part of the value for a foreach, we'll iterate over the lines automatically, placing each one in $_:

foreach (`who`) {
  my($user, $tty, $date) = /(\S+)\s+(\S+)\s+(.*)/;
  $ttys{$user} .= "$tty at $date\n";
}

This loop will iterate three times for the data above. (Your system will probably have more than three active logins at any given time.) Notice that we've got a regular expression match, and in the absence of the binding operator ("=~"), that's matching against $_, which is good because that's where the data is.

Also notice the regular expression is looking for a nonblank word, some whitespace, a nonblank word, some whitespace, and then the rest of the line up to, but not including, the newline (since dot doesn't match newline by default).[18] That's also good, because that's what the data looks like each time in $_. That'll make $1 be "merlyn", $2 be "tty/42", and $3 be "Dec 7 19:41", as a successful match on the first time through the loop.

[18] Now you can see why dot doesn't match newline by default. It makes it easy to write patterns like this one, in which we don't have to worry about a newline at the end of the string.

However, this regular expression match is in a list context, so instead of returning back a true/false value (as when you have a regular expression match in a scalar context), we take the memory variables and bundle them up in sequence as a list. In this case, the right side of that assignment is thus a three-element list, which happens to correspond to the three elements of the literal list on the left, and we get those nice corresponding assignments. So, $user ends up being "merlyn", and so on.

The second statement inside the loop simply stores away the tty and date information, appending to a (possibly undef) value in the hash, because a user might be logged in more than once, as user "rootbeer" was in our example.

 



Learning Perl
Learning Perl, 5th Edition
ISBN: 0596520107
EAN: 2147483647
Year: 2001
Pages: 205

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net