在我寻求用 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
也没有用。有人使用 qx
和 try
吗?
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.