【问题标题】:How to execute a shell script in Crystal while capturing output?如何在捕获输出的同时在 Crystal 中执行 shell 脚本?
【发布时间】:2016-05-30 19:26:18
【问题描述】:

我想在处理 stdout 和 stderr 输出时执行一个 shell 脚本。目前我使用Process.run、shell=false 和三个用于标准输入、标准输出和标准错误的管道执行命令。我生成纤程以从 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" |重击}

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水晶主" >/etc/apt/sources.list.d/crystal.list`

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

【问题讨论】:

  • 在第一种情况下,您需要将管道移动到bash -c 中才能调用它。不过,您正在与shell=false 作斗争,这是可以让您直接使用shell 语法的东西。
  • @Petesh 遗憾的是,设置 shell=true 没有任何帮助。
  • @Petesh 将管道移动到bash -c 调用似乎可行,但是。那么...为什么shell=true 不起作用?肯定是一样的吧?

标签: linux bash shell crystal-lang


【解决方案1】:

问题是 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)

【讨论】:

    【解决方案2】:

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

    没有shell=true,Process.run的默认行为是使用命令作为可执行文件来运行。这意味着字符串必须是程序名称,不带任何参数,例如uname 将是一个有效名称,因为我的系统上有一个名为 uname 的程序位于 /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 的命令。

    【讨论】:

    • 我认为问题在于我将命令字符串拆分为cmd(字符串)和args(数组),然后调用Process.new(cmd, args, ...)。即使使用 shell=true,它也不起作用 - 可能是因为我已经将 | 拆分为它自己的参数......
    • 基本上,我最终打电话给Process.new("curl", ["http://dist.crystal-lang.org/apt/setup.sh", "|", "bash"], shell: true)。当然,这没有用。为了支持shell=true,如果shell 为真,我需要修改我的包装函数以避免将命令拆分为单词。
    【解决方案3】:

    或者,如果您想调用 shell 脚本并获得我刚刚尝试使用水晶 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
    

    【讨论】:

      猜你喜欢
      • 2022-11-16
      • 1970-01-01
      • 2017-09-11
      • 2023-04-07
      • 1970-01-01
      • 2021-01-25
      • 2013-08-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多