Home > Raku > Ungolden silence

Ungolden silence

On my quest to replace Bash with Raku I tried to use qx and failed. It didn’t work for me because qx fails the wrong way. Under the hood Rakudo implements it by some grammar magic that forwards to QX in core.c/Proc.pm6 as follows.

sub QX($cmd, :$cwd = $*CWD, :$env) is implementation-detail {
    my $proc := Proc.new(:out);
    $proc.shell($cmd, :$cwd, :$env);
    $proc.out.slurp(:close) // Failure.new("Unable to read from '$cmd'")
}

This will return an empty Str, an non-empty Str or a Failure object. So this can be defined but False, defined but True or undefined but False. We get the undefined case when the shell that shell starts unexpectedly closes its STDOUT descriptor. We can get something that is False even if the command succeeds and we can get something that is True if the comand fails. On Unix silence is gold. Unless you provide -v or --verbose any command that does not need to output should not. If there is something wrong a non-zero exit code is returned and if ever possible something is written to STDERR. QX ignores exitcodes and stderr is forwarded to $*ERR. So we have little chance the catch it after the fact.

This behaviour will create hard to catch bugs. Let’s make a typo.

my $s = qx!True!;
say [$s.?defined, $s.?Bool, $s.?exitcode];
dd $not-proc;
/bin/sh: 1: True: not found
[True False (Any)]
Str $s = ""

Using the defined-or operator wont help here. Nor would try. Does anybody use qx and try?

dex@dexhome:~/projects/raku/rakudo/src$ ack 'QX'
core.c/Process.pm6
82: once if !Rakudo::Internals.IS-WIN && try { qx/id/ } -> $id {

On Windows that might actually fail as expected. On any other platform we need something sane.

sub sane-QX($cmd, :$cwd = $*CWD, :$env, :$quiet) {
    my $proc := Proc.new(:out, :err);
    $proc.shell($cmd, :$cwd, :$env);
    my $stdout = $proc.out.slurp(:close) // Failure.new("Unable to read from '$cmd'");
    my $stderr = $proc.err.slurp(:close);
    $*ERR.print: $stderr unless $quiet;
    if $proc.exitcode != 0 {
        return "" but role QXFail {
            method defined { False }
            method exitcode { $proc.exitcode }
            method err { $stderr }
        }
    }
    $stdout
}

my $sane = sane-QX(‚True‘);
say [$sane.?defined, $sane.?Bool, $sane.?exitcode, $sane.?err];
say sane-QX(‚True‘) // ‚I has a booboo!‘;

I’m not sure if just mixing undefinedness in is the right way. Maybe an exception with the exitcode and STDERR content would be better. That way the error handling can be moved into a CATCH block.

I will ponder this a little longer and then file a bug report. If it turns out the current implementation is desired the docs will need some warning stickers.

Categories: Raku
  1. cxw
    June 21, 2020 at 04:23

    Good catch! Have you opened an issue?

    • June 26, 2020 at 12:41

      Not yet, because I’m still unsure if I should.

  1. June 1, 2020 at 13:39

Leave a comment