我知道这是一个旧线程,但是 Simon Hürlimann 轻描淡写地提到了另一个选项。
关于这个主题的信息不多,我认为这可能会帮助其他有需要的人。
对于本示例,我们将使用 Open3,它使您能够同步或异步运行命令,并提供 stdout、stderr、退出代码 和 PID。
Open3 允许您访问标准输出、标准错误、退出代码和一个线程以在运行另一个程序时等待子进程。您可以使用与 Process.spawn 相同的方式指定程序的各种属性、重定向、当前目录等。 (来源:Open3 Docs)
我选择将输出格式化为CommandStatus 对象。这包含我们的stdout、stderr、pid(属于工作线程)和exitstatus。
class Command
require 'open3'
class CommandStatus
@stdout = nil
@stderr = nil
@pid = nil
@exitstatus = nil
def initialize(stdout, stderr, process)
@stdout = stdout
@stderr = stderr
@pid = process.pid
@exitstatus = process.exitstatus
end
def stdout
@stdout
end
def stderr
@stderr
end
def exit_status
@exitstatus
end
def pid
@pid
end
end
def self.execute(command)
command_stdout = nil
command_stderr = nil
process = Open3.popen3(ENV, command + ';') do |stdin, stdout, stderr, thread|
stdin.close
stdout_buffer = stdout.read
stderr_buffer = stderr.read
command_stdout = stdout_buffer if stdout_buffer.length > 0
command_stderr = stderr_buffer if stderr_buffer.length > 0
thread.value # Wait for Process::Status object to be returned
end
return CommandStatus.new(command_stdout, command_stderr, process)
end
end
cmd = Command::execute("echo {1..10}")
puts "STDOUT: #{cmd.stdout}"
puts "STDERR: #{cmd.stderr}"
puts "EXIT: #{cmd.exit_status}"
在读取 STDOUT/ERR 缓冲区时,我使用 command_stdout = stdout_buffer if stdout_buffer.length > 0 来控制是否分配了 command_stdout 变量。当没有数据存在时,您应该传递 nil 而不是 ""。以后交数据的时候就更清楚了。
你可能注意到我在使用command + ';'。这样做的原因是基于来自 Kernel.exec 的文档(这是 popen3 使用的):
如果第一种形式的字符串 (exec("command")) 遵循这些
简单的规则:
- 没有元字符
- 没有shell保留字,也没有特殊的内置
- Ruby 无需 shell 直接调用命令
您可以通过添加“;”来强制调用 shell到字符串(因为
“;”是一个元字符)
这只是防止 Ruby 在您传递格式错误的命令时抛出 'spawn': No such file or directory 错误。相反,它会直接将其传递给内核,在那里错误将被优雅地解决并显示为 STDERR 而不是未捕获的异常。