12 Part 10: Executing Commands with "open"

LinuxChix Perl Course Part 10: Executing Commands with "open"

1) Introduction
2) Accessing Command-Line Arguments
3) Executing Programs with "open"
4) When Not to Use "open"
5) A Simple Safety Rule
6) Exercises
7) Answer to Previous Exercise
8) Past Information
9) Credits
10) Licensing

-----------------------------------

1) Introduction

Last week we saw how to read from and write to files using the "open" command.
It turns out that "open" can also be used to execute programs and read their
output, or even send output to them. Part 10 explains how.

-----------------------------------

2) Accessing Command-Line Arguments

All of this working with "open" is making my head hurt, so let's start out with
a minor detour into something much easier.

Command-line arguments are passed to your program on the command line (hence
the name "command-line arguments"). Suppose that you have a Perl program called
"yourscript" and you execute it like this:

yourscript arg1 arg2 arg3

You could also execute it like this:

perl yourscript arg1 arg2 arg3

In either case, the "arg" parameters are command-line arguments.

Your program can access its first command-line argument by calling the "shift"
function. It can access the second command-line argument by calling "shift"
again, and so on. When there are no more parameters to return, "shift" returns
undefined. (The name "shift" comes from a shell script command with the same
name and a similar purpose.)

Try this program. Don't forget to pass command-line arguments, or it won't do
anything!

#!/usr/bin/perl -w
use strict;

while ( defined( my $person = shift ) ) {
print "Hi, $person!\n";
}

Note that we don't have to "chomp" command-line arguments because the operating
system removes the whitespace around them (unless of course the user explicitly
chooses to put whitespace around them).

Some people are surprised to learn that you CANNOT access command-line
arguments as $1, $2, etc. That's what you would do in a shell script, but in
Perl these variables are reserved for regular expression matching.
Interestingly enough, however, the variable $0 has a meaning in Perl similar to
its meaning in shell scripts.

By the way, command-line arguments can also be accessed via the array "@ARGV".
But since we haven't discussed arrays yet, we won't get into that.

-----------------------------------

3) Executing Programs with "open"

In addition to what we saw last week, the "open" command has one more very
powerful application: it allows you to execute a command, send input and
receive output.

Try this program (it only works on Unix):

#!/usr/bin/perl -w
use strict;

open DATA, "who |" or die "Couldn't execute program: $!";
while ( defined( my $line = <DATA> ) ) {
chomp($line);
print "$line\n";
}
close DATA;

Here's what happened: Perl saw that your "file" ended with a "pipe" (vertical
bar) character. So it interpreted the "file" as a command to be executed, and
interpreted the command's output as the "file"'s contents. The command is
"who" (which prints information on currently logged-in users). If you execute
that command, you will see that the output is exactly what the Perl program
gave you.

In this case, we "read" data from the command. To execute a command that we can
"write" (send data) to, we should place a pipe character BEFORE the command.
These options are mutually exclusive: we can read from a command or write to
it, but not both.

In the Unix world, a lot can be done by piping the output of one program into
the input of another. Perl continues this spirit.

Note that we can also send command-line parameters to the command, like this:

open DATA, "who -H |" or die "Couldn't execute program: $!";

In fact, Perl allows you to use "open" to do pretty much anything you would
normally do on the command-line, as this example demonstrates:

open OUTPUT, "| grep 'foo' > result.txt" or die "Failure: $!";

We can then write whatever we want to the "OUTPUT" filehandle. The Unix "grep"
command will filter out any text which doesn't contain the text "foo"; any text
which DOES contain "foo" will be written to "result.txt".

-----------------------------------

4) When Not to Use "open"

Don't overuse "open". This command should only be used when you need to pipe a
lot of data to or from a program.

For programs that give no output (or if you don't mind the output to going to
the screen), use the "system" command:

system "tar cf myArchive.tar *.c"; # Run "tar" to create an archive.

Note: "system" breaks the convention of returning true if the program succeeds.
Instead, it returns the exit code of the program plus some additional
information, or -1 if the program couldn't be executed at all. You can count on
"system" returning zero if the command succeeded, nonzero otherwise.

And for programs that only return a few lines of output, use back-ticks:

my $username = `whoami` or die "Couldn't execute command: $!";
chomp($username);
print "My name is $username\n";

You can also use the "qx//" operator, which is exactly equivalent to back-ticks
except that it allows you to choose your delimiter:

# All of the following are exactly equivalent
# to the above command that uses back-ticks.
my $username = qx/whoami/ or die "Couldn't execute command: $!";
my $username = qx(whoami) or die "Couldn't execute command: $!";
my $username = qx#whoami# or die "Couldn't execute command: $!";

-----------------------------------

5) A Simple Safety Rule

If you use the "open" command but don't specify whether you are opening for
reading, writing or piping, Perl will assume that you are opening for reading.
But DON'T DO THAT! Here's an example of why.

One of the common uses of Perl is CGI (web) scripting. As the Perl master in
your company, suppose you write a nice little dynamic web page that allows the
user to choose a department, then generates the list of the people in that
department. You write it like this:

open DEPARTMENT_DIRECTORY, "/usr/departments/$department" or die "...";
# Process and output the data.
close DEPARTMENT_DIRECTORY;

Well, one day someone will say, "hey, what happens if I enter
'../../bin/rm -fr / |'?"

Of course, what happens is that the command will do this:

open DEPARTMENT_DIRECTORY, "/bin/rm -fr / |";

The pipe (vertical bar) at the end of the file name means that it's a command
to be executed. In fact, it's that famous command that wipes out everything on
your hard drive.

But this problem is really, really easy to avoid: just put a "<" on the front
of the file you want to read. This will cause the "open" to fail if someone
tries to trick your script into doing something other than reading.

You have been warned.

-----------------------------------

6) Exercise

Some Unix systems include a "pidof" command, which outputs the PID(s)
associated with a currently running program:

[me@mybox]$ pidof bash
1032
1059

In the above example, there are two "bash" processes running, one with PID 1032
and one with PID 1059.

Write your own "pidof" program using Perl. Use the Unix command "ps -e" to read
all of the processes that are currently running.

-----------------------------------

7) Answer to Previous Exercise

Here is a program that reads a file (american.txt), "translates" it from
American to British spelling (at least partly), and writes the result to
another file (british.txt).

#!/usr/bin/perl
use strict;

open AMERICAN, '< american.txt' or die "Couldn't open file: $!";
open BRITISH, '> british.txt' or die "Couldn't open file: $!";

while ( defined(my $line = <AMERICAN>) ) {
$line =~ s/iz(e|es|ing)\b/is$1/g;
print BRITISH $line;
}

close AMERICAN;
close BRITISH;

-----------------------------------

8) Past Information

Part 1: Getting Started
http://linuxchix.org/pipermail/courses/2003-March/001147.html

Part 2: Scalar Data
http://linuxchix.org/pipermail/courses/2003-March/001153.html

Part 3: User Input
http://linuxchix.org/pipermail/courses/2003-April/001170.html

Part 4: Control Structures
http://linuxchix.org/pipermail/courses/2003-April/001184.html

Part 4.5, a review with a little new information at the end:
http://linuxchix.org/pipermail/courses/2003-July/001297.html

Part 5: The "tr///" Operator
http://linuxchix.org/pipermail/courses/2003-July/001302.html

Part 6: The "m//" Operator
http://linuxchix.org/pipermail/courses/2003-August/001305.html

Part 7: More About "m//"
http://linuxchix.org/pipermail/courses/2003-August/001322.html

Part 8: The "s///" Operator
http://linuxchix.org/pipermail/courses/2003-August/001330.html

Part 9: Simple File Access
http://linuxchix.org/pipermail/courses/2003-September/001340.html

-----------------------------------

9) Credits

Works cited:
a) "man perlop"
b) "man perlopentut"
c) Kirrily Robert, Paul Fenwick and Jacinta Richardson's "Intermediate Perl",
which you can find (along with their "Introduction to Perl") at:
http://www.perltraining.com.au/notes.html

Thanks to Jacinta Richardson for fact checking.

-----------------------------------

10) Licensing

This course (i.e., all parts of it) is copyright 2003 by Alice Wood and Dan
Richter, and is released under the same license as Perl itself (Artistic
License or GPL, your choice). This is the license of choice to make it easy
for other people to integrate your Perl code/documentation into their own
projects. It is not generally used in projects unrelated to Perl.