在 Windows 上将标准输入转发到 Ruby 中的子进程
Forwarding stdin to a subprocess in Ruby on Windows
我正在为 Terraform 开发一个包装器,在执行期间的某个时刻,它可能会请求用户输入。因此,我的应用程序必须将其标准输入中键入的所有内容转发到子进程的标准输入。以下解决方案适用于 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 中)能够将它们的标准输入转发到 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
的代码片段似乎有效。它执行命令,returns 命令输出为包含输出行的数组。可选地,输出也写入标准输出。
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("\r\n", '') }
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
我正在为 Terraform 开发一个包装器,在执行期间的某个时刻,它可能会请求用户输入。因此,我的应用程序必须将其标准输入中键入的所有内容转发到子进程的标准输入。以下解决方案适用于 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 中)能够将它们的标准输入转发到 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
的代码片段似乎有效。它执行命令,returns 命令输出为包含输出行的数组。可选地,输出也写入标准输出。
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("\r\n", '') }
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