Project

General

Profile

Actions

Bug #14198

closed

Error forwarding standard input to subprocess

Added by betabandido (Victor Jimenez) over 6 years ago. Updated over 4 years ago.

Status:
Closed
Assignee:
-
Target version:
-
ruby -v:
ruby 2.4.2p198 (2017-09-14 revision 59899) [x64-mingw32]
[ruby-core:84334]

Description

I am developing a wrapper for Terraform (https://www.terraform.io/), which at some point during its execution, it may request user input. So, my application must forward everything typed on its stdin to the subprocess' stdin. The following solution works on Linux, but on Windows the subprocess (Terraform) seems to never receive the input.

A similar approach works for wrappers implemented in Python or Go, so I believe this may actually be a bug in Ruby itself.

The file test.rb contains the code to reproduce the issue. First, Terraform is downloaded in the working directory. Then, popen3 is used to run Terraform.


Files

Gemfile (46 Bytes) Gemfile betabandido (Victor Jimenez), 12/18/2017 01:29 PM
test.rb (1.27 KB) test.rb betabandido (Victor Jimenez), 12/18/2017 01:29 PM

Updated by shyouhei (Shyouhei Urabe) over 6 years ago

I din't have a windows machine right now so not sure this is the solution for you. But JFYI you can reroute the IOs by specifying arguments to spawn like this:

def exec(cmd)
  pid = Process.spawn(cmd, in: STDIN, out: STDOUT, err: STDERR)
  Process.waitpid pid
end

Updated by betabandido (Victor Jimenez) over 6 years ago

Using Process.spawn indeed works :) Unfortunately, I cannot figure out a way to forward the standard output and standard error to the console (i.e, STDOUT and STDERR) as well as storing that output in a variable.

The example in test.rb is a minimal example that just reproduces the issue where the subprocess does not receive any data on its standard input. But, the real code is a little bit more complex as it needs to store the output generated by the subprocess too.

Is there any easy way to do so? Is fork + exec and using pipes the only way to go?

Updated by nobu (Nobuyoshi Nakada) over 6 years ago

system also accepts redirections.
And child processes inherit STDIN/STDOUT/STDERR from their parent process by the default, you don't need such arguments at all usually.
If you want to capture the outputs and the errors, IO.popen(%w'terraform destroy', err: [:child, :out], &:read).

Actions #4

Updated by nobu (Nobuyoshi Nakada) over 6 years ago

  • Status changed from Open to Feedback

Updated by betabandido (Victor Jimenez) over 6 years ago

Thanks for the suggestions.

The following code snippet based on IO.popen seems to work. It executes a command, and it returns its output as an array containing the output lines. Optionally, the output is written to stdout too.

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

I am not sure whether there is an easier way to accomplish this, but this seems good enough.

Actions #6

Updated by jeremyevans0 (Jeremy Evans) over 4 years ago

  • Status changed from Feedback to Closed
Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0