将STDIN转发到Windows Ruby中的子过程中



我正在为Terraform开发一个包装器,在执行过程中的某个时候,它可能会请求用户输入。因此,我的应用程序必须将其在STDIN上输入的所有内容转发到子过程的STDIN。以下解决方案在Linux上起作用,但是在Windows子过程中(Terraform)似乎永远不会收到输入:

require 'open3'
def exec(cmd)
  Open3.popen3(cmd) do |stdin, stdout, stderr, thread|
    stdout_thread = Thread.new do
      IO.copy_stream(stdout, STDOUT)
    end
    stderr_thread = Thread.new do
      IO.copy_stream(stderr, STDERR)
    end
    stdin_thread = Thread.new do
      IO.copy_stream(STDIN, stdin)
    end
    puts "Return code: #{thread.value}"
    stdin_thread.join
    stdout_thread.join
    stderr_thread.join
  end
end
exec('terraform destroy')

执行某些需要用户输入与Terraform不同的应用程序时,此解决方案实际上在Windows上工作。但是,以下两个实现(在Go和Python中)能够将其Stdin转发到Windows上的Terraform。因此,可能是我的Ruby代码存在一些问题,或者在处理过程执行和输入转发时,Ruby对Windows的实现有一定限制。

有人知道这种限制吗?

python示例:

import subprocess
import sys
with subprocess.Popen(['terraform', 'destroy'],
                      stdin=sys.stdin, stdout=sys.stdout) as proc:
    proc.wait()

去示例:

package main
import (
    "io"
    "log"
    "os"
    "os/exec"
)
func main() {
    cmd := exec.Command("terraform", "destroy")
    stdin, err := cmd.StdinPipe()
    if err != nil { log.Fatal(err) }
    stdout, err := cmd.StdoutPipe()
    if err != nil { log.Fatal(err) }
    stderr, err := cmd.StderrPipe()
    if err != nil { log.Fatal(err) }
    go func() {
        defer stdout.Close()
        io.Copy(os.Stdout, stdout)
    }()
    go func() {
        defer stderr.Close()
        io.Copy(os.Stderr, stderr)
    }()
    go func() {
        defer stdin.Close()
        io.Copy(stdin, os.Stdin)
    }()
    err = cmd.Run()
    log.Printf("Command finished with error: %v", err)
}

基于IO.popen的以下代码段似乎有效。它执行命令,并将命令输出返回,作为包含输出行的数组。可选地,输出也写入stdout。

def run(cmd, directory: Dir.pwd, print_output: true)
  out = IO.popen(cmd, err: %i[child out], chdir: directory) do |io|
    begin
      out = ''
      loop do
        chunk = io.readpartial(4096)
        print chunk if print_output
        out += chunk
      end
    rescue EOFError; end
    out
  end
  $?.exitstatus.zero? || (raise "Error running command #{cmd}")
  out.split("n")
     .map { |line| line.tr("rn", '') }
end

@betabandido这也有效。

def run(cmd, directory: Dir.pwd, print_output: true)
  out = IO.popen(cmd, err: %i[child out], chdir: directory) do |io|
    io.readlines
  end
  raise "Error running command #{cmd}" unless $?.exitstatus.zero?
  print out if print_output
  out
end

最新更新