Inter-process communication
Running programs
Many programs need to be able to run other programs, and we need to pass information to them and receive their output and exit status. Running a program in Raku is as easy as:
run 'git', 'status';
This line runs the program named "git" and passes "git" and "status" to its
command-line. It will find the program using the %*ENV<PATH>
setting.
If you would like to run a program by sending a command-line to the shell, there's a tool for that as well. All shell metacharacters are interpreted by the shell, including pipes, redirects, environment variable substitutions and so on.
shell 'ls -lR | gzip -9 > ls-lR.gz';
Caution should be taken when using shell
with user input.
The Proc object
Both run
and shell
return a Proc object, which can be used
to communicate with the process in more detail. Please note that unless you
close all output pipes, the program will usually not terminate.
my $git = run 'git', 'log', '--oneline', :out;
for $git.out.lines -> $line {
my ($sha, $subject) = $line.split: ' ', 2;
say "$subject [$sha]";
}
$git.out.close();
If the program fails (exits with a non-zero exit code), it will throw an exception when the returned Proc object is sunk. You can save it into a variable, even anonymous one, to prevent the sinking:
$ = run '/bin/false'; # does not sink the Proc and so does not throw
You can tell the Proc object to capture output as a filehandle by passing
the :out
and :err
flags. You may also pass input via the :in
flag.
my $echo = run 'echo', 'Hello, world', :out;
my $cat = run 'cat', '-n', :in($echo.out), :out;
say $cat.out.get;
$cat.out.close();
You may also use Proc to capture the PID, send signals to the application, and check the exitcode.
my $crontab = run 'crontab', '-l';
if $crontab.exitcode == 0 {
say 'crontab -l ran ok';
}
else {
say 'something went wrong';
}
Example with shell
Suppose there is a utility that will takes input from STDIN and outputs to STDOUT, but there isn't yet a Raku wrapper. For this example, lets use sass, which preprocesses a more convenient form of CSS called SCSS into standard CSS. In a terminal the utility would be used as sass --stdin
We want to convert multiple strings of SCSS into CSS. The code below only converts one string and captures the output into a variable, but it should be obvious how to adapt this into a loop.
#| process with utility, first check it exists in the environment
my $proc = shell( <<sass --version>>, :out, :merge);
exit note 'Cannot run sass' unless $proc.out.slurp(:close) ~~ / \d \. \d+ /;
# start loop here for multiple SCSS strings
# set up the process for stdin.
$proc = shell( <<sass --stdin --style=compressed>>, :in, :err, :out );
my $scss = q:to/SCSS/;
div.rendered-formula {
display: flex;
justify-content: space-around;
align-items: center;
img.logo {
align-self: center;
}
}
SCSS
# now pipe the SCSS string into the process as STDIN (remembering to close the pipe)
$proc.in.spurt($scss,:close);
# extract the output or the error
my $error = $_ with $proc.err.slurp(:close);
my $output = $_ with $proc.out.slurp(:close);
# end loop here
The Proc::Async object
When you need more control over the communication with and from another process, you will want to make use of Proc::Async. This class provides support for asynchronous communication with a program, as well as the ability to send signals to that program.
# Get ready to run the program
my $log = Proc::Async.new('tail', '-f', '/var/log/system.log');
$log.stdout.tap(-> $buf { print $buf });
$log.stderr.tap(-> $buf { $*ERR.print($buf) });
# Start the program
my $done = $log.start;
sleep 10;
# Tell the program to stop
$log.kill('QUIT');
# Wait for the program to finish
await $done;
The small program above uses the "tail" program to print out the contents of the
log named system.log
for 10 seconds and then tells the program to stop with a
QUIT signal.
Whereas Proc provides access to output using IO::Handles, Proc::Async provides access using asynchronous supplies (see Supply).
If you want to run a program and do some work while you wait for the original
program to finish, the start
routine returns a Promise,
which is kept when the program quits.
Use the write
method to pass data into the program.