如何从 System.cmd 输出中捕获每一行?

How to capture each line from System.cmd output?

当 运行 命令来自 System.cmd 或通过其他方法时,如何捕获控制台中写入的每一行?

我什至想要捕获最终结果,但在这种情况下控制台中显示的内容类似于:Cloning into 'myrepo'... remote: Counting objects: 3271, done. 并通过频道发送每一行:

case System.cmd("git", ["clone", "git@github.com:#{vault}/#{repo}.git"], cd: repo) do
  {results, 0} ->
    Myapp.Endpoint.broadcast("app:setup", "new:line", results)
  {_, code} ->
    raise RuntimeError, "`git clone` failed with code #{code}"
end

我还没有找到解决方案,有类似的问题但没有明确的答案:question or question

所以,这里有几种方法,我将尝试总结一下,让你能够理解之前在其他问题中的答案。

首先,你应该知道详细处理这个东西,你需要学习如何使用:erlang.open_port/2。您可以将 {:line, max_length} 传递给选项以每行获取 1 条消息。您看到的 git 的输出是正在写入 stderr 的内容,您可以传递 :stderr_to_stdout 进行重定向,以便它们在每条消息中出现 1 行。您可以循环使用 receive 直到收到 eof 消息,您可以查看文档以了解有关何时发出 eof 消息的更多详细信息。

bitwalker 在你的第二个 link 中的回答将通过一些修改得到你想要的:

defmodule Shell do
  def exec(exe, args, opts \ [:stream]) when is_list(args) do
    port = Port.open({:spawn_executable, exe}, opts ++ [{:args, args}, :binary, :exit_status, :hide, :use_stdio, :stderr_to_stdout])
    handle_output(port)
  end

  def handle_output(port) do
    receive do
      {^port, {:data, data}} ->
        IO.inspect(data) # Replace this with the appropriate broadcast
        handle_output(port)
      {^port, {:exit_status, status}} ->
        status
    end
  end
end

现在,虽然我们可以将 stderr 重定向到 stdout,但这里的问题之一是 git 将检测重定向的流并相应地调整它的流量。我希望您比较这些调用的输出:

gitcmd = System.find_executable("git")
Shell.exec(gitcmd, ["clone","--progress",url], [{:line, 4096}])

这为它在流中找到的每个“\n”打印出 1 条消息。注意到所有带 \r 的垃圾了吗?进度就是这样出来的。您可以从 args 中删除 --progress,您只会得到 1 或 2 行无聊的行。如果你想得到所有的东西,你需要流式传输结果,然后自己按行分割输出。

Shell.exec(gitcmd, ["clone","--progress",url], [:stream])

您可以看到,使用 :stream 而不是 :line 我们将获得从另一侧刷新的所有内容。您必须自己清理悬挂的 \r\n,并且您可能想要计划接收部分线路,但我认为这应该让您顺利开始旅程。

这一切都是关于将 stderr 重定向到 stdout,但是如果你需要实际保留 stderrstdout 之间的区别,并且你想要终止进程的能力,而不是依赖在您关闭 stdin 后关闭它时,您将不得不依赖诸如 porcelain 之类的东西来管理一个行为良好的进程,该进程将为您 "manage" 另一个进程。