如何在捕获输出时在 Crystal 中执行 shell 脚本?

How to execute a shell script in Crystal while capturing output?

我想在处理 stdout 和 stderr 输出时执行 shell 脚本。目前,我使用 Process.runshell=false 和三个用于 stdin、stdout 和 stderr 的管道来执行命令。我生成纤程以从 stdout 和 stderr 读取并记录(或以其他方式处理)输出。这对于单个命令非常有效,但对于脚本却非常失败。

我可以在调用 Process.run 时简单地设置 shell=true,但查看 Crystal 源代码似乎只是将 "sh" 添加到命令行。我试过在前面加上 "bash" 但没有用。

重定向 (>file) 和管道(例如 curl something | bash)之类的东西似乎不适用于 Process.run

例如,要下载一个shell脚本并执行它,我试过:

cmd = %{bash -c "curl http://dist.crystal-lang.org/apt/setup.sh" | bash}

Process.run(cmd, ...)

添加了最初的 bash,希望它能启用管道运算符。这似乎没有帮助。我还尝试分别执行每个命令:

script.split("\n").reject(/^#/, "").each { Process.run(...) }

当然,当命令使用重定向或管道时,这仍然会失败。例如,命令 echo "deb http://dist.crystal-lang.org/apt crystal main" >/etc/apt/sources.list.d/crystal.list 只是输出:

"deb http://dist.crystal-lang.org/apt crystal main" >/etc/apt/sources.list.d/crystal.list`

如果我改用 `` 反引号执行方法,它可能会起作用;但这样我就无法实时捕获输出。

我的理解是基于阅读 run.cr 文件的源代码。该行为在处理命令和参数方面与其他语言非常相似。

没有 shell=trueProcess.run 的默认行为是使用命令作为 可执行文件 到 运行。这意味着该字符串需要是一个程序名称,不带任何参数,例如uname 是一个有效的名称,因为我的系统上有一个名为 uname 的程序,位于 /usr/bin.

如果您曾经成功地将 %{bash -c "echo hello world"}shell=false 一起使用,那么一定有问题 - 默认行为应该是尝试 运行 一个名为 [=19] 的程序=],这不太可能存在于任何系统上。

一旦你传入 'shell=true',它就会执行 sh -c <command>,这将允许像 echo hello world 这样的字符串作为命令工作;这也将允许重定向和管道工作。

shell=true 行为通常可以解释为执行以下操作:

cmd = "sh"
args = [] of String
args << "-c" << "curl http://dist.crystal-lang.org/apt/setup.sh | bash"
Process.run(cmd, args, …)

请注意,我在这里使用了一个参数数组 - 如果没有参数数组,您将无法控制参数如何传递到 shell。

The reason why the first version, with or without shell=true doesn't work is because the pipeline is outside the -c, which is the command you're sending to bash.

问题是 UNIX 问题。父进程必须能够访问子进程的 STDOUT。使用管道,您必须启动一个 shell 进程,它将 运行 整个命令,包括 | bash 而不仅仅是 curl $URL。在 Crystal 中是:

command = "curl http://dist.crystal-lang.org/apt/setup.sh | bash"
io = MemoryIO.new
Process.run(command, shell: true, output: io)
output = io.to_s

或者,如果您想复制 Crystal 为您做的事情:

Process.run("sh", {"-c", command}, output: io)

或者如果你想调用 shell 脚本并获得我刚刚尝试使用 crystal 0.23.1 的输出并且它工作!

def screen
    output = IO::Memory.new
     Process.run("bash", args: {"lib/bash_scripts/installation.sh"}, output: output)
     output.close
    output.to_s
end