Rash - A Raku Shell

Part 6: The Process

by Arne Sommer

Rash - Part 6: The Process

[67.6] Published 13. April 2020.

See also: The Introduction | Part 1: The Path | Part 2: The Loop | Part 3: The Execution | Part 4: The Interrupt | Part 5: The Dynamic.

The Proc Object

When we run a program with run or shell we get a Proc object. We can redirect in- and output on it.

Let us revisit this command from Part 3: The Execution:

shell 'ls -lR | gzip -9 > ls-lR.gz';

Here it is implement with run and Proc:

File: run-io
my $fh = open :w, :bin, "ls-lR.gz";

my $proc1 = run <ls -lR>,  :out;
my $proc2 = run <gzip -9>, :in($proc1.out), :out($fh);
      
$fh.close;

And as an unreadable one-liner without all the variables (and shown here on two lines to make it somewhat easier to read):

File: run-io2
run <gzip -9>, :in((run <ls -lR>,  :out).out),
               :out(open :w, :bin, "ls-lR.gz");

This works with in- and ouput for shell as well, but it is easier to let the shell do it for us (as shown in the example).

See docs.raku.org/type/Proc for more information about Proc.

We can handle STDERR as well, with :err.

We can catch the output, by calling out on the Proc object:

> my $proc   = run <ls -lR>, :out;
> my $output = $proc.out.slurp(:close);

Note that :close is required, as the filehandle will be left open otherwise.

Except that it isn't really a filehandle, but a IO.Pipe object:

> my $proc = run <ls -lR>, :out;
> say $proc.out.WHAT;
(Pipe)

See docs.perl6.org/type/IO::Pipe for more information about IO.Pipe.

WHAT is a Metamethod that gives the type of the object. See docs.raku.org/language/mop#WHAT for more information.

Quote and Run

Quoting is normally done with 'single quotes' (which does not interpolate variables) and "double quotes" (which does interpolate them).

We can also use the q/.../ or qq/.../ construct, where q is the same as single quoting, and qq is the same as double quoting:

> my $a = 122;
> say q/This is $a/;   # -> This is $a
> say qq/This is $a/;  # -> This is 122

qx/.../ (Quote eXecute)

This quoting construct will execute (as a program) whatever is inside the quotes.

If we use qx, the return value is the output from the program. And we have no way of getting at the Proc object (and status codes).

> say "** " ~ qx/date/ ~ " **";
** Sa. 11. April 13:43:11 +0200 2020
 **

Note that most programs add a newline to the end, and qx does not remove it. This is where chomp, which removes a trailing newline character, comes in handy:

> say "** " ~ qx/date/.chomp ~ " **";
** Sa. 11. April 13:43:11 +0200 2020 **

The shell will complain (to STDOUT) if it doesn't find the program:

> say "** " ~ qx/foo.bar/.chomp ~ " **";
/bin/sh: 1: foo.bar: not found
**  **

See docs.raku.org/language/routine/chomp for more information about chomp.

The qx version of the «hostname» program looks like this:

File: hostname-qx
print qx/hostname/;

We use print as the «hostname» program has added a newline for us (and one is enough).

The shell version is shorter:

File: hostname-shell
shell("hostname");

The program handles the output, so we don't even have to do a print. (If we do, we get the Proc object.)

We can do it the hard way, by capturing the output and printing it ourself:

File: hostname-proc
my $proc = shell "hostname", :out;
my $host = $proc.out.slurp(:close);

print $host;

We can swap shell with run, but it doesn't make a difference in this case.

qqx/.../ (Quote eXecute with interpolation)

qx does not not do variable interpolation. Use qqx if you require interpolation:

File: hostname-qqx
my $cmd = "hostname";

print qqx/$cmd/;

Be very careful if you do this, as you may end up running harmful commands.

When not to Execute

It is always a good idea to check for alternatives. Raku actually supplies the hostname out of the box in the dynamic variable $*KERNEL:

File: hostname-kernel
say $*KERNEL.hostname;

Part 7: The Promise

There will be a 7th part «The Promise» in a couple of weeks, and that is a promise.