Thor:运行命令而不捕获stdout或stderr,并在出错时失败

时间:2021-12-30 20:58:59

I'm writing a Thor script to run some tests from a different tool i.e. running a shell command. I'd like the stdout and stderr from the command to continuously stream out into my console.

我正在编写一个Thor脚本,以便从另一个工具运行一些测试,即运行shell命令。我希望命令中的stdout和stderr不断流入我的控制台。

First attempt was to just use backticks, but naturally the stdout/stderr are not printed (rather, stdout is captured in the return value).

第一次尝试只是使用反引号,但自然不会打印stdout / stderr(而是在返回值中捕获stdout)。

desc "mytask", "my description"
def mytask
  `run-my-tests.sh`
end

My next approach was to use Open3 as in:

我的下一个方法是使用Open3,如下所示:

require "open3"
desc "mytask", "my description"
def mytask
  Open3.popen3("run-my-tests.sh") do |stdin, stdout, stderr|
    STDOUT.puts(stdout.read())
    STDERR.puts(stderr.read())
  end
end

However, the above approach will get the whole output from both stdout and stderr and only print at the end. Un my use case, I'd rather see the output of failing and passing tests as it becomes available.

但是,上面的方法将从stdout和stderr获得整个输出,并且只在最后打印。在我的用例中,我宁愿看到失败和传递测试的输出,因为它变得可用。

From http://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html, I saw that we can read the streams by chunks i.e. with gets() instead of read(). For example:

从http://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html,我看到我们可以通过块读取流,即使用gets()而不是read()。例如:

require "open3"
desc "mytask", "my description"
def mytask
  Open3.popen3(command) do |stdin, stdout, stderr|
    while (out = stdout.gets()) || err = (stderr.gets())
      STDOUT.print(out) if out
      STDERR.print(err) if err
    end
    exit_code = wait_thr.value
    unless exit_code.success?
      raise "Failure"
    end
  end
end

Does it look like the best and cleanest approach? Is it an issue that I have to manually try to print stdout before stderr?

它看起来是最好,最干净的方法吗?这是一个我必须手动尝试在stderr之前打印stdout的问题吗?

2 个解决方案

#1


0  

I'm using IO.popen for similar task, like so: IO.popen([env, *command]) do |io| io.each { |line| puts ">>> #{line}" } end To capture stderr I'd just redirect it to stdout command = %w(run-my-tests.sh 2>&1)

我正在使用IO.popen执行类似的任务,如下所示:IO.popen([env,* command])do | io | io.each {| line | puts“>>>#{line}”} end要捕获stderr,我只需将其重定向到stdout命令=%w(run-my-tests.sh 2>&1)

Update I've constructed a script using Open3::popen3 to capture stdout and stderr separately. It obviously has a lot of room form improvement, but basic idea hopefully is clear.

更新我使用Open3 :: popen3构建了一个脚本,分别捕获stdout和stderr。它显然有很多房型改进,但基本的想法很有希望。

require 'open3'

command = 'for i in {1..5}; do echo $i; echo "$i"err >&2; sleep 0.5; done'
stdin, stdout, stderr, _command_thread = Open3.popen3(command)

reading_thread = Thread.new do
  kilobyte = 1024

  loop do
    begin
      stdout.read_nonblock(kilobyte).lines { |line| puts "stdout >>> #{line}" }
      stderr.read_nonblock(kilobyte).lines { |line| puts "stderr >>> #{line}" }
    rescue IO::EAGAINWaitReadable
      next
    rescue EOFError
      break
    end

    sleep 1
  end
end

reading_thread.join
stdin.close
stdout.close
stderr.close

#2


0  

Seems to me like the simplest way to run a shell command and not try to capture the stdout or stderr (instead, let them bubble up as they come) was something like:

在我看来,这是运行shell命令的最简单方法,而不是尝试捕获stdout或stderr(相反,让它们在它们出现时冒泡)是这样的:

def run *args, **options
  pid = spawn(*args, options)
  pid, status = Process.wait2(pid)
  exit(status.exitstatus) unless status.success?
end

The problem with backticks or system() is that the former captures the stdout and the latter only returns whether the command succeeded or not. spawn() is a more informative alternative to system(). I'd rather have my Thor script tool fail as if it was merely a wrapper for those shell commands.

反引号或system()的问题是前者捕获stdout而后者只返回命令是否成功。 spawn()是system()的更有用信息的替代品。我宁愿让我的Thor脚本工具失败,好像它只是那些shell命令的包装器。

#1


0  

I'm using IO.popen for similar task, like so: IO.popen([env, *command]) do |io| io.each { |line| puts ">>> #{line}" } end To capture stderr I'd just redirect it to stdout command = %w(run-my-tests.sh 2>&1)

我正在使用IO.popen执行类似的任务,如下所示:IO.popen([env,* command])do | io | io.each {| line | puts“>>>#{line}”} end要捕获stderr,我只需将其重定向到stdout命令=%w(run-my-tests.sh 2>&1)

Update I've constructed a script using Open3::popen3 to capture stdout and stderr separately. It obviously has a lot of room form improvement, but basic idea hopefully is clear.

更新我使用Open3 :: popen3构建了一个脚本,分别捕获stdout和stderr。它显然有很多房型改进,但基本的想法很有希望。

require 'open3'

command = 'for i in {1..5}; do echo $i; echo "$i"err >&2; sleep 0.5; done'
stdin, stdout, stderr, _command_thread = Open3.popen3(command)

reading_thread = Thread.new do
  kilobyte = 1024

  loop do
    begin
      stdout.read_nonblock(kilobyte).lines { |line| puts "stdout >>> #{line}" }
      stderr.read_nonblock(kilobyte).lines { |line| puts "stderr >>> #{line}" }
    rescue IO::EAGAINWaitReadable
      next
    rescue EOFError
      break
    end

    sleep 1
  end
end

reading_thread.join
stdin.close
stdout.close
stderr.close

#2


0  

Seems to me like the simplest way to run a shell command and not try to capture the stdout or stderr (instead, let them bubble up as they come) was something like:

在我看来,这是运行shell命令的最简单方法,而不是尝试捕获stdout或stderr(相反,让它们在它们出现时冒泡)是这样的:

def run *args, **options
  pid = spawn(*args, options)
  pid, status = Process.wait2(pid)
  exit(status.exitstatus) unless status.success?
end

The problem with backticks or system() is that the former captures the stdout and the latter only returns whether the command succeeded or not. spawn() is a more informative alternative to system(). I'd rather have my Thor script tool fail as if it was merely a wrapper for those shell commands.

反引号或system()的问题是前者捕获stdout而后者只返回命令是否成功。 spawn()是system()的更有用信息的替代品。我宁愿让我的Thor脚本工具失败,好像它只是那些shell命令的包装器。