Документация
HTML CSS PHP PERL другое

Section 14.1.  The system Function

 
Previous
Table of Contents
Next

14.1. The system Function

The simplest way to launch a child process in Perl to run a program is the system function. For example, to invoke the Unix date command from within Perl, it looks like this:

    system "date";

The child process runs the date command, which inherits Perl's standard input, standard output, and standard error. This mean the normal short date-and-time string generated by date ends up wherever Perl's STDOUT was going.

The parameter to the system function is generally whatever you'd normally type at the shell. If it were a more complicated command, like "ls -l $HOME", we'd just have put all that into the parameter:

    system 'ls -l $HOME';

We had to switch here from double quotes to single quotes since $HOME is the shell's variable. Otherwise, the shell would never have seen the dollar sign since that's an indicator for Perl to interpolate. Alternatively, we could write this:

    system "ls -l \$HOME";

But that can become unwieldly.

The date command is output-only, but say it had been a chatty command, asking first "for which time zone do you want the time?"[*] That'll end up on standard output, and then the program will listen on standard input (inherited from Perl's STDIN) for the response. You'll see the question, type in the answer (like "Zimbabwe time"), and date will finish its duty.

[*] As far as we know, no one has made a date command that works like this.

While the child process is running, Perl is patiently waiting for it to finish. If the date command took 37 seconds, then Perl is paused for those 37 seconds. You can use the shell's facility to launch a background process[Section 14.1.  The system Function]:

[Section 14.1.  The system Function] See what we mean about this depending upon your system? The Unix shell (/bin/sh) lets you use the ampersand on this kind of command to make a background process. If your non-Unix system doesn't support this way to launch a background process, then you can't do it this way.

    system "long_running_command with parameters &";

Here the shell launches, notices the ampersand at the end of the command line, and puts long_running_command into the background. And then the shell exits quickly, which Perl notices and moves on. In this case, the long_running_command is a grandchild of the Perl process, to which Perl has no direct access or knowledge.

When the command is "simple enough," no shell gets involved. For the date and ls commands, the requested command is launched directly by Perl, which searches the inherited PATH[Section 14.1.  The system Function] to find the command if necessary. But if there's anything weird in the string (such as shell metacharacters like the dollar sign, semicolon, or vertical bar), Perl invokes the standard Bourne Shell (/bin/sh[§]) to work through the complicated stuff. In that case, the shell is the child process, and the requested commands are grandchildren (or further offspring). For example, you can write an entire little shell script in the following argument:

[Section 14.1.  The system Function] The PATH can be changed by adjusting $ENV{'PATH'} at any time. Initially, this is the environment variable inherited from the parent process (usually the shell). Changing this value affects new child processes but cannot affect any preceding parent processes. The PATH is the list of directories where executable programs (commands) are found, even on some non-Unix systems.

[§] Or whatever was determined when Perl was built. Practically, this is /bin/sh on Unix-like systems.

    system 'for i in *; do echo =  = $i =  =; cat $i; done';

Here again, we're using single quotes because the dollar signs are meant for the shell and not for Perl. Double quotes would have permitted Perl to expand $i to its current Perl value and not let the shell expand it to its own value.[**] By the way, that little shell script goes through all of the normal files in the current directory, printing out each one's name and contents; you can try it if you don't believe us.

[**] If you set $i = '$i', it would work anyway until a maintenance programmer came along and "fixed" that line out of existence.

14.1.1. Avoiding the Shell

The system operator may be invoked with more than one argument,[*] in which case a shell doesn't get involved no matter how complicated the text:

[*] Or with a parameter in the indirect-object slot, like system { 'fred' } 'barney';, which runs the program barney, but lies to it so it thinks it's called 'fred'. See the perlfunc manpage.

    my $tarfile = "something*wicked.tar";
    my @dirs = qw(fred|flintstone <barney&rubble> betty );
    system "tar", "cvf", $tarfile, @dirs;

In this case, the first parameter ("tar" here) gives the name of a command found in the normal PATH-searching way, and the remaining arguments are passed, one by one, directly to that command. Even if the arguments have shell-significant characters, such as the name in $tarfile or the directory names in @dirs, the shell never gets a chance to mangle the string. So, that tar command will get five parameters. Compare this with:

    system "tar cvf $tarfile @dirs";  # Oops!

Here, we've now piped a bunch of stuff into a flintstone command, put it into the background, and opened betty for output.

That's a bit scary,[Section 14.1.  The system Function] especially if those variables are from user input, such as from a web form or something. So, if you can arrange things to use the multiple-argument version of system, you probably should use that way to launch your subprocess. (You'll have to give up the ability to have the shell do the work for you to set up I/O redirection, background processes, and the like, though. There's no such thing as a free launch.)

[Section 14.1.  The system Function] Unless you're using taint checking and have done all the right things to prescan your data to ensure that the user isn't pulling a fast one on you.

Note that redundantly, a single-argument invocation of system is nearly equivalent to the proper multiple-argument version of system:

    system $command_line;
    system "/bin/sh", "-c", $command_line;

But nobody writes the latter unless you want things processed by a different shell, like the C-shell:

    system "/bin/csh", "-fc", $command_line;

Even this is rare since the One True Shell[*] seems to have a lot more flexibility, especially for scripted items.

[*] That's /bin/sh, or whatever your Unix system has installed as the most Bourne-like shell. If you don't have a One True Shell, Perl figures out how to invoke some other command-line interpreter, with notable consequences. See the documentation for that Perl port.

The return value of the system operator is based upon the exit status of the child command[Section 14.1.  The system Function]. In Unix, an exit value of 0 means that everything is all right, and a nonzero exit value usually indicates that something went wrong:

[Section 14.1.  The system Function] It's the "wait" status, which is the child exit code times 256, plus 128 if core was dumped, plus the signal number triggering termination if any. But we rarely check the specifics of that, and a true/false value suffices for nearly all applications.

    unless (system "date") {
      # Return was zero - meaning success
      print "We gave you a date, OK!\n";
    }

This is backward from the normal "true is goodfalse is bad" strategy for most of the operators, so to write a typical "do this or die" style, we'll need to flip false and true. The easiest way is to prefix the system operator with a bang (the logical-not operator):

    !system "rm -rf files_to_delete" or die "something went wrong";

In this case, including $! in the error message would be inappropriate because the failure is most likely somewhere within the experience of the rm command, and it's not a system-call related error within Perl that $! can reveal.

    Previous
    Table of Contents
    Next
    © 2000- NIV