4

I am used to using open3 to run commands in Ruby. Since there doesn't seem to be an equivalent lib in crystal-lang, I kludged up this:

    def run_cmd(cmd, args)
      stdout_str = IO::Memory.new
      stderr_str = IO::Memory.new
      result = [] of Int32 | String
      status = Process.run(cmd, args: args, output: stdout_str, error: stderr_str)
      if status.success?
        result = [status.exit_code, "#{stdout_str}"]
      else
        result = [status.exit_code, "#{stderr_str}"]
      end
      stdout_str.close
      stderr_str.close
      result
    end

    cmd = "ping"
    hostname = "my_host"
    args = ["-c 2", "#{hostname}"]
    result = run_cmd(cmd, args)
    puts "ping: #{hostname}: Name or service not known" if result[0] != 0

Is there a better way to do this? Says the retired network specialist who is not a software developer exploring crystal-lang.

Thanks in advance for all advice.

lewis
  • 131
  • 1
  • 9

1 Answers1

14

Probably this:

def run_cmd(cmd, args)
  stdout = IO::Memory.new
  stderr = IO::Memory.new
  status = Process.run(cmd, args: args, output: stdout, error: stderr)
  if status.success?
    {status.exit_code, stdout.to_s}
  else
    {status.exit_code, stderr.to_s}
  end
end

We don't need to close an IO::Memory because it doesn't represent a handle to any OS resources, just a block of memory, and we use tuples instead of arrays for the return. This means the callers know we're returning exactly two items and the first is a number and the second is a string. With an array return the caller only knows we're returning any number of items, any of which could be either an int32 or a string.

You can then use it like this:

cmd = "ping"
hostname = "my_host"
args = ["-c 2", hostname]
status, output = run_cmd(cmd, args)
puts "ping: #{hostname}: Name or service not known" unless status == 0
Johannes Müller
  • 5,581
  • 1
  • 11
  • 25
Stephie
  • 3,135
  • 17
  • 22
  • Sweet! Thank you so very much. – lewis Nov 03 '17 at 20:12
  • I've landed on pretty much the same thing but I was initially using `stdout.gets_to_end` which yielded an empty string. `to_s` works as expected. I'm surprised by this behavior. What causes it? – harm Sep 29 '21 at 10:06