沉默不语

Ungolden silence

在我寻求用 Raku 替换 Bash 的过程中,我尝试使用 qx,但失败了。对我来说,它没有工作,因为 qx 失败的方式是错误的。在底层,Rakudo 通过一些语法魔法来实现它,在 core.c/Proc.pm6 中转发到 qx,如下所示。

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'")
}

这将返回一个空的 Str,一个非空的 Str 或一个 Failure 对象。所以这可以是 defined but False, defined but True 或 undefined but False。当 shell 启动的 shell 意外地关闭了它的 STDOUT 描述符时,我们会得到未定义的情况。我们可以得到一些即使命令成功也是 False 的东西,也可以得到一些如果命令失败也是 True 的东西。在 Unix 上,沉默是金。除非你提供 -v--verbose,否则任何不需要输出的命令都不应该输出。如果有什么问题,一个非零的退出代码会被返回,如果可能的话,一些东西会被写入 STDERR。QX 会忽略退出代码,STDERR 会转发到 $*ERR。所以我们几乎没有机会在事后发现它。

这种行为会造成难以发现的错误。让我们做一个错别字。

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 = ""

使用 defined-or 操作符在这里没有帮助。try 也没有用。有人使用 qxtry 吗?

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

在 Windows 上,这实际上可能会失败,因为预期。在任何其他平台上,我们需要一些合理的东西。

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!‘;

我不知道只是把未定义混进去是不是正确的方法。也许用 exitcode 和 STDERR 内容的异常会更好。这样一来,错误处理就可以移到一个 CATCH 块中。

我会再思考一下,然后提交一个错误报告。如果事实证明当前的实现是需要的,那么文档将需要一些警告便签

by gfldex.

comments powered by Disqus