我想在处理标准输出和标准输出时执行一个shell脚本。目前,我使用 Process.run
执行命令,shell=false
和三个管道分别用于 stdin、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("").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'
如果我使用 ``
反引号执行方法,它可能会起作用;但这样我就无法实时捕获输出。
问题是 UNIX 问题。父进程必须能够访问子进程的 STDOUT。使用管道,您必须启动一个 shell 进程,该进程将运行整个命令,包括| bash
而不仅仅是curl $URL
。在水晶中,这是:
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)
我的理解是基于阅读run.cr
文件的源代码。该行为在处理命令和参数的方式上与其他语言非常相似。
如果没有shell=true
,Process.run
的默认行为是使用该命令作为可执行文件来运行。这意味着字符串必须是程序名称,没有任何参数,例如 uname
将是一个有效名称,因为我的系统上有一个名为 uname
的程序 in /usr/bin
.
如果你曾经有过成功使用%{bash -c "echo hello world"}
的行为,那么shell=false
,那么就有问题了 - 默认行为应该是尝试运行一个名为bash -c "echo hello world"
的程序,它不太可能存在于任何系统上。
一旦你传入了'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 中。
第一个版本(带或不带
shell=true
)不起作用的原因是管道在-c
之外,这是您要发送到 bash 的命令。
或者如果你想调用一个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