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.
Good catch! Have you opened an issue?
Not yet, because I’m still unsure if I should.