Cleaning up IO#popen

I think IO#popen could stand to be changed, and I wanted to run my ideas by
this list. Currently the problems with popen are:

* can't bypass the shell
* takes a "mode string" arg for what really should be a separate function
* takes a "-" as a magic value for what really should be a separate function
* generally kludgy

While looking around for ways to improve on it I came across the Open3
module in the stdlib, which has a much more "ruby" interface. Based mostly on
that, I redid some methods that could replace IO.popen (and put popen3 in IO
at the same time, which is where it probably belongs)

The only problem I've identified with what I have below is that I close
stderr, and this may be wrong. I also have a version that doesn't close
stderr, but I don't know which is the Right Way (Perl's IPC::Open2 doesn't
close stderr).

Can I get some opinions? There's quite a few core ruby methods that seem to
exist simply because there's some C function by that name, and that leaves
the language with a definate non-ruby feel. This is intended to replace the
current popen, and make all the popen calls consistant in operation. There's
also IO.fork_open, which is my rendition of IO.popen('-').

Thanks in advance for your input!

# Shamelessly copied from the Open3 module

def IO.popen3(cmd)
  pw = IO::pipe # pipe[0] for read, pipe[1] for write
  pr = IO::pipe
  pe = IO::pipe
  
  pid = fork{
    # child
    fork{
      # grandchild
      pw[1].close
      STDIN.reopen(pw[0])
      pw[0].close
      
      pr[0].close
      STDOUT.reopen(pr[1])
      pr[1].close
      
      pe[0].close
      STDERR.reopen(pe[1])
      pe[1].close
      
      exec(*cmd)
    }
    exit!
  }
  
  pw[0].close
  pr[1].close
  pe[1].close
  Process.waitpid(pid)
  pi = [pw[1], pr[0], pe[0]]
  pw[1].sync = true
  if block_given?
    begin
      return yield(*pi)
    ensure
      pi.each{|p| p.close unless p.closed?}
    end
  end
  pi
end

def IO.popen2(cmd, &block)
  ret = IO.popen3(cmd, &block)
  if block_given?
    return ret
  else
    ret[2].close
    return [ret[0], ret[1]]
  end
end

def IO.popen(cmd, &block)
  ret = IO.popen3(cmd, &block)
  if block_given?
    return ret
  else
    ret[2].close
    ret[0].close
    return ret[1]
  end
end

def IO.fork_open
  if block_given?
    rd,wr = IO::pipe
    begin
      if pid=fork
        yield(rd,pid)
      else
        yield(wr,pid)
        exit!
      end
      Process.waitpid(pid)
    ensure
      rd.close
      wr.close
    end
  end
end

Hi,

At Mon, 21 Jun 2004 06:06:02 +0900,
evanm@frap.net wrote in [ruby-talk:104194]:

I think IO#popen could stand to be changed, and I wanted to run my ideas by
this list. Currently the problems with popen are:

* can't bypass the shell

In 1.9, it can as well as Kernel#system.

While looking around for ways to improve on it I came across the Open3
module in the stdlib, which has a much more "ruby" interface. Based mostly on
that, I redid some methods that could replace IO.popen (and put popen3 in IO
at the same time, which is where it probably belongs)

I don't feel that Open3 is more ruby-like, but it's rather
perlish.

···

--
Nobu Nakada

Hey,

Hi,

At Mon, 21 Jun 2004 06:06:02 +0900,
evanm@frap.net wrote in [ruby-talk:104194]:
>
> I think IO#popen could stand to be changed, and I wanted to run my ideas by
> this list. Currently the problems with popen are:
>
> * can't bypass the shell

In 1.9, it can as well as Kernel#system.

Really? I went and grabbed ruby-1.9-rodc-20040114.tgz to check it out, and
the docs say it's the same. Is there a better place to find this information?
Is the mode argument gone? Is the magic "-" value gone?

> While looking around for ways to improve on it I came across the Open3
> module in the stdlib, which has a much more "ruby" interface. Based mostly on
> that, I redid some methods that could replace IO.popen (and put popen3 in IO
> at the same time, which is where it probably belongs)

I don't feel that Open3 is more ruby-like, but it's rather
perlish.

To me, magic values in a string are hackish. popen('-') logically is and should
be an entirely separate function. I can't really comment on the new version of
popen that you're referencing as I can't find any information on it. How does
it work? What is its args? Does it drop the '-' and mode arguments?

···

On Mon, Jun 21, 2004 at 11:34:53AM +0900, nobu.nokada@softhome.net wrote:

Hi,

At Mon, 21 Jun 2004 22:02:32 +0900,
evanm@frap.net wrote in [ruby-talk:104243]:

> > * can't bypass the shell
>
> In 1.9, it can as well as Kernel#system.

Really? I went and grabbed ruby-1.9-rodc-20040114.tgz to check it out, and
the docs say it's the same. Is there a better place to find this information?

Sorry, I've forgotten to update the document.

Is the mode argument gone? Is the magic "-" value gone?

Both are still there.

You can give arguments as an Array. e.g.:

  IO.popen(%w[echo |arguments| contain shell's <meta> "chars"], "r")

> > While looking around for ways to improve on it I came across the Open3
> > module in the stdlib, which has a much more "ruby" interface. Based mostly on
> > that, I redid some methods that could replace IO.popen (and put popen3 in IO
> > at the same time, which is where it probably belongs)
>
> I don't feel that Open3 is more ruby-like, but it's rather
> perlish.

To me, magic values in a string are hackish. popen('-') logically is and should
be an entirely separate function. I can't really comment on the new version of
popen that you're referencing as I can't find any information on it. How does
it work? What is its args? Does it drop the '-' and mode arguments?

I'm not sure they are really evil.

At Mon, 21 Jun 2004 06:06:02 +0900,
evanm@frap.net wrote in [ruby-talk:104194]:

* takes a "mode string" arg for what really should be a separate function
* takes a "-" as a magic value for what really should be a separate function

Do you suggest to provide all possible combinations?

···

--
Nobu Nakada

You can give arguments as an Array. e.g.:

  IO.popen(%w[echo |arguments| contain shell's <meta> "chars"], "r")

Great, that's also a workable way.

> it work? What is its args? Does it drop the '-' and mode arguments?

I'm not sure they are really evil.

Parsing a string for magic arguments moves logic from the code into the data.
It's poor design. Why not have a separate function with all the benefits that
brings? AFAIK there aren't any benefits in parsing magic values from a
string, but if you allow "-" then the programmer has to special case every
string he passes to verify that it's not "-" or risk running the wrong
function.

There are many more elegant ways of doing this, but the best is to not do it
at all.

At Mon, 21 Jun 2004 06:06:02 +0900,
evanm@frap.net wrote in [ruby-talk:104194]:
> * takes a "mode string" arg for what really should be a separate function
> * takes a "-" as a magic value for what really should be a separate function

Do you suggest to provide all possible combinations?

I suggest the code I submitted for IO.fork_open. There are only two
options, reading and writing, and this can be done in one with my
implementation of IO.fork_open which provides a bidirectional pipe.

IO.pread and IO.pwrite would be preferable to IO.popen with a mode argument,
IMHO.

Evan

···

On Tue, Jun 22, 2004 at 08:32:06AM +0900, nobu.nokada@softhome.net wrote:

Hi,

At Wed, 23 Jun 2004 02:49:26 +0900,
evanm@frap.net wrote in [ruby-talk:104358]:

> > it work? What is its args? Does it drop the '-' and mode arguments?
>
> I'm not sure they are really evil.

Parsing a string for magic arguments moves logic from the code into the data.
It's poor design. Why not have a separate function with all the benefits that
brings? AFAIK there aren't any benefits in parsing magic values from a
string, but if you allow "-" then the programmer has to special case every
string he passes to verify that it's not "-" or risk running the wrong
function.

There are many more elegant ways of doing this, but the best is to not do it
at all.

I admit there will be better ways much. But I'd like to
introduce a new class for child process rather than popen3.

> At Mon, 21 Jun 2004 06:06:02 +0900,
> evanm@frap.net wrote in [ruby-talk:104194]:
> > * takes a "mode string" arg for what really should be a separate function
> > * takes a "-" as a magic value for what really should be a separate function
>
> Do you suggest to provide all possible combinations?

I suggest the code I submitted for IO.fork_open. There are only two
options, reading and writing, and this can be done in one with my
implementation of IO.fork_open which provides a bidirectional pipe.

Single pipe pair cannot be used for bidirectional, and
fork_open doesn't feel a good name for me.

···

--
Nobu Nakada

i like 'spoon', or 'spork' pesonally. :wink:

-a

···

On Thu, 24 Jun 2004 nobu.nokada@softhome.net wrote:

Single pipe pair cannot be used for bidirectional, and fork_open doesn't
feel a good name for me.

--

EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
PHONE :: 303.497.6469
A flower falls, even though we love it;
and a weed grows, even though we do not love it. --Dogen

===============================================================================

I admit there will be better ways much. But I'd like to
introduce a new class for child process rather than popen3.

Sure. My main concern is looking to the functions in the base classes and
stdlib, and seeing inconsistant argument handling. I agree that a lot of this
should live in a new class, but where possible I'd try to standardize the
interface, even between IO and some new class.

Single pipe pair cannot be used for bidirectional, and
fork_open doesn't feel a good name for me.

Pardon me, I meant to say that it leaves both ends open. Perhaps fork_read or
fork_write? I don't like the name either, but it's all I could think of.

···

On Thu, Jun 24, 2004 at 12:04:45PM +0900, nobu.nokada@softhome.net wrote:

Hi,

At Thu, 24 Jun 2004 12:43:10 +0900,
Ara.T.Howard wrote in [ruby-talk:104437]:

> Single pipe pair cannot be used for bidirectional, and fork_open doesn't
> feel a good name for me.

i like 'spoon', or 'spork' pesonally. :wink:

I never knew the trademark, thank you. I think it is call
"point splitted spoon" in Japanese.

···

--
Nobu Nakada

Nobu wrote:

Ara.T.Howard wrote in [ruby-talk:104437]:
> Nobu wrote:
> > [...] fork_open doesn't feel a good name for me.
>
> i like 'spoon', or 'spork' pesonally. :wink:

I never knew the trademark, thank you. I think it is call
"point splitted spoon" in Japanese.

I haven't heard 'spork' until now, either.

Hope it will become available as a mix-in
through module DrainVegetable.

daz

(spork®)

http://oak.cats.ohiou.edu/~jm703496/spork/anatomy.html

(slotted spoon)
http://www.cutleryandmore.com/tools/details.asp?SKU=4034