Chapter 14. Process ManagementContents: The system Function One of the best parts of being a programmer is launching someone else's code so that you don't have to write it yourself. It's time to learn how to manage your children[304] by launching other programs directly from Perl.
And like everything else in Perl, There's More Than One Way To Do It, with lots of overlap, variations, and special features. So if you don't like the first way, just read on for another page or two for a solution more to your liking. Perl is very portable; most of the rest of this book doesn't need many notes saying that it works this way on Unix systems and that way on Windows and the other way on VMS. But when you're starting other programs on your machine, different programs are available on a Macintosh than you'll likely find on a Cray. The examples in this chapter are primarily Unix-based; if you have a non-Unix system, you can expect to see some differences. 14.1. The system FunctionThe 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: system "date"; The child process runs the date command, which inherits Perl's standard input, standard output, and standard error. This mean that the normal short date-and-time string generated by date ends up wherever Perl's STDOUT was already going. The parameter to the system function is generally whatever you'd normally type at the shell. So 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'; Note that 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 also an indicator for Perl to interpolate. Alternatively, we could write: system "ls -l \$HOME"; But that can get quickly unwieldly. Now, the date command is output-only, but let's say it had been a chatty command, asking first "for which time zone do you want the time?"[305] 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, and type in the answer (like "Zimbabwe time"), and then date will finish its duty.
While the child process is running, Perl is patiently waiting for it to finish. So 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,[306] however:
system "long_running_command with parameters &"; Here, the shell gets launched, which then notices the ampersand at the end of the command line, causing the long_running_command to be made into a background process. And then the shell exits rather quickly, which Perl notices and moves on. In this case, the long_running_command is really a grandchild of the Perl process, to which Perl really has no direct access or knowledge. When the command is "simple enough," no shell gets involved, so for the date and ls commands earlier, the requested command is launched directly by Perl, which searches the inherited PATH[307] 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), then the standard Bourne Shell (/bin/sh[308]) gets invoked 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 argument:
system 'for in *; do echo == $i ==; cat $i; done'; Here again, we're using single quotes, because the dollar signs here 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.[309] 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 out yourself if you don't believe us.
14.1.1. Avoiding the ShellThe system operator may also be invoked with more than one argument,[310] in which case a shell doesn't get involved, no matter how complicated the text:
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, while 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 precisely five parameters. Compare this with: system "tar cvf $tarfile @dirs"; # Oops! Here, we've now piped a bunch of stuff into a flintstone command and put it into the background, and opened betty for output. And that's a bit scary,[311] especially if those variables are from user input -- such as from a web form or something. So, if you can arrange things so that you can 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.)
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 to be processed by a different shell, like the C-shell: system "/bin/csh", "-fc", $command_line; Even this is pretty rare, since the One True Shell[312] seems to have a lot more flexibility, especially for scripted items.
The return value of the system operator is based upon the exit status of the child command[313]. In Unix, an exit value of 0 means that everything is OK, and a non-zero exit value usually indicates that something went wrong:
unless (system "date") { # Return was zero - meaning success print "We gave you a date, OK!\n"; } Note that this is backward from the normal "true is good -- false 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 simply 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 not be appropriate, 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. Copyright © 2002 O'Reilly & Associates. All rights reserved. |
|