summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog11
-rw-r--r--lib/open3.rb317
-rw-r--r--test/test_open3.rb138
3 files changed, 327 insertions, 139 deletions
diff --git a/ChangeLog b/ChangeLog
index 4363053491..f516ecae82 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+Thu Dec 4 19:56:20 2008 Tanaka Akira <akr@fsij.org>
+
+ * lib/open3.rb (Open3.popen3): simplified.
+ (Open3.popen_run): extracted from Open3.popen3.
+ (Open3.popen2): new method.
+ (Open3.popen2e): new method.
+ (Open3.pipeline_rw): new method.
+ (Open3.pipeline_r): new method.
+ (Open3.pipeline_w): new method.
+ (Open3.pipeline_run): new private method.
+
Thu Dec 4 19:16:28 2008 Tanaka Akira <akr@fsij.org>
* process.c (check_exec_fds): resolve cascaded child fd reference.
diff --git a/lib/open3.rb b/lib/open3.rb
index 454cd44ee4..55915bb512 100644
--- a/lib/open3.rb
+++ b/lib/open3.rb
@@ -51,7 +51,7 @@ module Open3
# stdin, stdout, stderr, wait_thr = Open3.popen3(cmd... [, opts])
# pid = wait_thr[:pid] # pid of the started process.
# ...
- # stdin.close # stdin, stdout and stderr should be closed in this form.
+ # stdin.close # stdin, stdout and stderr should be closed explicitly in this form.
# stdout.close
# stderr.close
# exit_status = wait_thr.value # Process::Status object returned.
@@ -61,6 +61,7 @@ module Open3
#
# Open3.popen3("echo a") {|i, o, e, t| ... }
# Open3.popen3("echo", "a") {|i, o, e, t| ... }
+ # Open3.popen3(["echo", "argv0"], "a") {|i, o, e, t| ... }
#
# If the last parameter, opts, is a Hash, it is recognized as an option for Kernel#spawn.
#
@@ -68,75 +69,105 @@ module Open3
# p o.read.chomp #=> "/"
# }
#
- # opts[STDIN], opts[STDOUT] and opts[STDERR] in the option are set for redirection.
- #
- # If some of the three elements in opts are specified,
- # pipes for them are not created.
- # In that case, block arugments for the block form and
- # return values for the non-block form are decreased.
+ # wait_thr.value waits the termination of the process.
+ # The block form also waits the process when it returns.
#
- # # No pipe "e" for stderr
- # Open3.popen3("echo a", STDERR=>nil) {|i,o,t| ... }
- # i,o,t = Open3.popen3("echo a", STDERR=>nil)
+ # Closing stdin, stdout and stderr does not wait the process.
#
- # If the value is nil as above, the elements of opts are removed.
- # So standard input/output/error of current process are inherited.
+ def popen3(*cmd, &block)
+ if Hash === cmd.last
+ opts = cmd.pop.dup
+ else
+ opts = {}
+ end
+
+ in_r, in_w = IO.pipe
+ opts[STDIN] = in_r
+ in_w.sync = true
+
+ out_r, out_w = IO.pipe
+ opts[STDOUT] = out_w
+
+ err_r, err_w = IO.pipe
+ opts[STDERR] = err_w
+
+ popen_run(cmd, opts, [in_r, out_w, err_w], [in_w, out_r, err_r], &block)
+ end
+ module_function :popen3
+
+ # Open3.popen2 is similer to Open3.popen3 except it doesn't make a pipe for
+ # the standard error stream.
#
- # If the value is not nil, it is passed as is to Kernel#spawn.
- # So pipeline of commands can be constracted as follows.
+ # Block form:
#
- # Open3.popen3("yes", STDIN=>nil, STDERR=>nil) {|o1,t1|
- # Open3.popen3("head -10", STDIN=>o1, STDERR=>nil) {|o2,t2|
- # o1.close
- # p o2.read #=> "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"
- # p t1.value #=> #<Process::Status: pid 13508 SIGPIPE (signal 13)>
- # p t2.value #=> #<Process::Status: pid 13510 exit 0>
- # }
+ # Open3.popen2(cmd... [, opts]) {|stdin, stdout, wait_thr|
+ # pid = wait_thr.pid # pid of the started process.
+ # ...
+ # exit_status = wait_thr.value # Process::Status object returned.
# }
#
- # wait_thr.value waits the termination of the process.
- # The block form also waits the process when it returns.
- #
- # Closing stdin, stdout and stderr does not wait the process.
+ # Non-block form:
+ #
+ # stdin, stdout, wait_thr = Open3.popen2(cmd... [, opts])
+ # ...
+ # stdin.close # stdin and stdout should be closed explicitly in this form.
+ # stdout.close
#
- def popen3(*cmd)
+ def popen2(*cmd, &block)
if Hash === cmd.last
opts = cmd.pop.dup
else
opts = {}
end
- child_io = []
- parent_io = []
-
- if !opts.include?(STDIN)
- pw = IO.pipe # pipe[0] for read, pipe[1] for write
- opts[STDIN] = pw[0]
- pw[1].sync = true
- child_io << pw[0]
- parent_io << pw[1]
- elsif opts[STDIN] == nil
- opts.delete(STDIN)
- end
+ in_r, in_w = IO.pipe
+ opts[STDIN] = in_r
+ in_w.sync = true
- if !opts.include?(STDOUT)
- pr = IO.pipe
- opts[STDOUT] = pr[1]
- child_io << pr[1]
- parent_io << pr[0]
- elsif opts[STDOUT] == nil
- opts.delete(STDOUT)
- end
+ out_r, out_w = IO.pipe
+ opts[STDOUT] = out_w
+
+ popen_run(cmd, opts, [in_r, out_w], [in_w, out_r], &block)
+ end
+ module_function :popen2
- if !opts.include?(STDERR)
- pe = IO.pipe
- opts[STDERR] = pe[1]
- child_io << pe[1]
- parent_io << pe[0]
- elsif opts[STDERR] == nil
- opts.delete(STDERR)
+ # Open3.popen2e is similer to Open3.popen3 except it merges
+ # the standard output stream and the standard error stream.
+ #
+ # Block form:
+ #
+ # Open3.popen2e(cmd... [, opts]) {|stdin, stdout_and_stderr, wait_thr|
+ # pid = wait_thr.pid # pid of the started process.
+ # ...
+ # exit_status = wait_thr.value # Process::Status object returned.
+ # }
+ #
+ # Non-block form:
+ #
+ # stdin, stdout_and_stderr, wait_thr = Open3.popen2e(cmd... [, opts])
+ # ...
+ # stdin.close # stdin and stdout_and_stderr should be closed explicitly in this form.
+ # stdout_and_stderr.close
+ #
+ def popen2e(*cmd, &block)
+ if Hash === cmd.last
+ opts = cmd.pop.dup
+ else
+ opts = {}
end
+ in_r, in_w = IO.pipe
+ opts[STDIN] = in_r
+ in_w.sync = true
+
+ out_r, out_w = IO.pipe
+ opts[[STDOUT, STDERR]] = out_w
+
+ popen_run(cmd, opts, [in_r, out_w], [in_w, out_r], &block)
+ end
+ module_function :popen2e
+
+ def popen_run(cmd, opts, child_io, parent_io) # :nodoc:
pid = spawn(*cmd, opts)
wait_thr = Process.detach(pid)
child_io.each {|io| io.close }
@@ -151,7 +182,185 @@ module Open3
end
result
end
- module_function :popen3
+ module_function :popen_run
+ class << self
+ private :popen_run
+ end
+
+ # Open3.pipeline_rw starts list of commands as a pipeline with pipes
+ # which connects stdin of the first command and stdout of the last command.
+ #
+ # Open3.pipeline_rw(cmd1, cmd2, ... [, opts]) {|stdin, stdout, wait_threads|
+ # ...
+ # }
+ #
+ # stdin, stdout, wait_threads = Open3.pipeline_rw(cmd1, cmd2, ... [, opts])
+ # ...
+ # stdin.close
+ # stdout.close
+ #
+ # Each cmd is a string or an array.
+ # If it is an array, the elements are passed to Kernel#spawn.
+ #
+ # The option to pass Kernel#spawn is constructed by merging
+ # +opts+, the last hash element of the array and
+ # specification for the pipe between each commands.
+ #
+ # Example:
+ #
+ # Open3.pipeline_rw("sort", "cat -n") {|stdin, stdout, wait_thrs|
+ # stdin.puts "foo"
+ # stdin.puts "bar"
+ # stdin.puts "baz"
+ # stdin.close # send EOF to sort.
+ # p stdout.read #=> " 1\tbar\n 2\tbaz\n 3\tfoo\n"
+ # }
+ def pipeline_rw(*cmds, &block)
+ if Hash === cmds.last
+ opts = cmds.pop.dup
+ else
+ opts = {}
+ end
+
+ in_r, in_w = IO.pipe
+ opts[STDIN] = in_r
+ in_w.sync = true
+
+ out_r, out_w = IO.pipe
+ opts[STDOUT] = out_w
+
+ pipeline_run(cmds, opts, [in_r, out_w], [in_w, out_r], &block)
+ end
+ module_function :pipeline_rw
+
+ # Open3.pipeline_r starts list of commands as a pipeline with a pipe
+ # which connects stdout of the last command.
+ #
+ # Open3.pipeline_r(cmd1, cmd2, ... [, opts]) {|stdout, wait_threads|
+ # ...
+ # }
+ #
+ # stdout, wait_threads = Open3.pipeline_r(cmd1, cmd2, ... [, opts])
+ # ...
+ # stdout.close
+ #
+ # Example:
+ #
+ # Open3.pipeline_r("yes", "head -10") {|r, ts|
+ # p r.read #=> "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"
+ # p ts[0].value #=> #<Process::Status: pid 24910 SIGPIPE (signal 13)>
+ # p ts[1].value #=> #<Process::Status: pid 24913 exit 0>
+ # }
+ #
+ def pipeline_r(*cmds, &block)
+ if Hash === cmds.last
+ opts = cmds.pop.dup
+ else
+ opts = {}
+ end
+
+ out_r, out_w = IO.pipe
+ opts[STDOUT] = out_w
+
+ pipeline_run(cmds, opts, [out_w], [out_r], &block)
+ end
+ module_function :pipeline_r
+
+ # Open3.pipeline_w starts list of commands as a pipeline with a pipe
+ # which connects stdin of the first command.
+ #
+ # Open3.pipeline_w(cmd1, cmd2, ... [, opts]) {|stdin, wait_threads|
+ # ...
+ # }
+ #
+ # stdin, wait_threads = Open3.pipeline_w(cmd1, cmd2, ... [, opts])
+ # ...
+ # stdin.close
+ #
+ # Example:
+ #
+ # Open3.pipeline_w("cat -n", "bzip2 -c", STDOUT=>"/tmp/z.bz2") {|w, ts|
+ # w.puts "hello"
+ # w.close
+ # p ts[0].value
+ # p ts[1].value
+ # }
+ #
+ def pipeline_w(*cmds, &block)
+ if Hash === cmds.last
+ opts = cmds.pop.dup
+ else
+ opts = {}
+ end
+
+ in_r, in_w = IO.pipe
+ opts[STDIN] = in_r
+ in_w.sync = true
+
+ pipeline_run(cmds, opts, [in_r], [in_w], &block)
+ end
+ module_function :pipeline_w
+
+ def pipeline_run(cmds, pipeline_opts, child_io, parent_io, &block) # :nodoc:
+ if cmds.empty?
+ raise ArgumentError, "no commands"
+ end
+
+ opts_base = pipeline_opts.dup
+ opts_base.delete STDIN
+ opts_base.delete STDOUT
+
+ wait_thrs = []
+ r = nil
+ cmds.each_with_index {|cmd, i|
+ cmd_opts = opts_base.dup
+ if String === cmd
+ cmd = [cmd]
+ else
+ cmd_opts.update cmd.pop if Hash === cmd.last
+ end
+ if i == 0
+ if !cmd_opts.include?(STDIN)
+ if pipeline_opts.include?(STDIN)
+ cmd_opts[STDIN] = pipeline_opts[STDIN]
+ end
+ end
+ else
+ cmd_opts[STDIN] = r
+ end
+ if i != cmds.length - 1
+ r2, w2 = IO.pipe
+ cmd_opts[STDOUT] = w2
+ else
+ if !cmd_opts.include?(STDOUT)
+ if pipeline_opts.include?(STDOUT)
+ cmd_opts[STDOUT] = pipeline_opts[STDOUT]
+ end
+ end
+ end
+ pid = spawn(*cmd, cmd_opts)
+ wait_thrs << Process.detach(pid)
+ r.close if r
+ w2.close if w2
+ r = r2
+ }
+ result = parent_io + [wait_thrs]
+ child_io.each {|io| io.close }
+ if defined? yield
+ begin
+ return yield(*result)
+ ensure
+ parent_io.each{|io| io.close unless io.closed?}
+ wait_thrs.each {|t| t.join }
+ end
+ end
+ result
+ end
+ module_function :pipeline_run
+ class << self
+ private :pipeline_run
+ end
+
end
if $0 == __FILE__
diff --git a/test/test_open3.rb b/test/test_open3.rb
index a484d4fb9b..7d45eb543e 100644
--- a/test/test_open3.rb
+++ b/test/test_open3.rb
@@ -74,21 +74,6 @@ class TestOpen3 < Test::Unit::TestCase
}
end
- def test_disable
- Open3.popen3(RUBY, '-e', '', STDIN=>nil) {|o,e,t|
- assert_kind_of(Thread, t)
- }
- Open3.popen3(RUBY, '-e', '', STDOUT=>nil) {|i,e,t|
- assert_kind_of(Thread, t)
- }
- Open3.popen3(RUBY, '-e', '', STDERR=>nil) {|i,o,t|
- assert_kind_of(Thread, t)
- }
- Open3.popen3(RUBY, '-e', '', STDIN=>nil, STDOUT=>nil, STDERR=>nil) {|t|
- assert_kind_of(Thread, t)
- }
- end
-
def with_pipe
r, w = IO.pipe
yield r, w
@@ -106,100 +91,83 @@ class TestOpen3 < Test::Unit::TestCase
old.close if old && !old.closed?
end
- def test_disable_stdin
- with_pipe {|r, w|
- with_reopen(STDIN, r) {|old|
- Open3.popen3(RUBY, '-e', 's=STDIN.read; STDOUT.print s+"o"; STDERR.print s+"e"', STDIN=>nil) {|o,e,t|
- assert_kind_of(Thread, t)
- w.print "x"
- w.close
- assert_equal("xo", o.read)
- assert_equal("xe", e.read)
- }
- }
- }
- end
-
- def test_disable_stdout
+ def test_popen2
with_pipe {|r, w|
- with_reopen(STDOUT, w) {|old|
+ with_reopen(STDERR, w) {|old|
w.close
- Open3.popen3(RUBY, '-e', 's=STDIN.read; STDOUT.print s+"o"; STDERR.print s+"e"', STDOUT=>nil) {|i,e,t|
+ Open3.popen2(RUBY, '-e', 's=STDIN.read; STDOUT.print s+"o"; STDERR.print s+"e"') {|i,o,t|
assert_kind_of(Thread, t)
- i.print "y"
+ i.print "z"
i.close
- STDOUT.reopen(old)
- assert_equal("yo", r.read)
- assert_equal("ye", e.read)
+ STDERR.reopen(old)
+ assert_equal("zo", o.read)
+ assert_equal("ze", r.read)
}
}
}
end
- def test_disable_stderr
+ def test_popen2e
with_pipe {|r, w|
with_reopen(STDERR, w) {|old|
w.close
- Open3.popen3(RUBY, '-e', 's=STDIN.read; STDOUT.print s+"o"; STDERR.print s+"e"', STDERR=>nil) {|i,o,t|
+ Open3.popen2e(RUBY, '-e', 's=STDIN.read; STDOUT.print s+"o"; STDOUT.flush; STDERR.print s+"e"') {|i,o,t|
assert_kind_of(Thread, t)
i.print "y"
i.close
STDERR.reopen(old)
- assert_equal("yo", o.read)
- assert_equal("ye", r.read)
+ assert_equal("yoye", o.read)
+ assert_equal("", r.read)
}
}
}
end
- def test_plug_pipe
- Open3.popen3(RUBY, '-e', 'STDOUT.print "1"') {|i1,o1,e1,t1|
- Open3.popen3(RUBY, '-e', 'STDOUT.print STDIN.read+"2"', STDIN=>o1) {|o2,e2,t2|
- assert_equal("12", o2.read)
+ def test_pipeline_rw
+ Open3.pipeline_rw([RUBY, '-e', 'print STDIN.read + "1"'],
+ [RUBY, '-e', 'print STDIN.read + "2"']) {|i,o,ts|
+ assert_kind_of(IO, i)
+ assert_kind_of(IO, o)
+ assert_kind_of(Array, ts)
+ assert_equal(2, ts.length)
+ ts.each {|t| assert_kind_of(Thread, t) }
+ i.print "0"
+ i.close
+ assert_equal("012", o.read)
+ ts.each {|t|
+ assert(t.value.success?)
}
}
end
- def test_tree_pipe
- ia,oa,ea,ta = Open3.popen3(RUBY, '-e', 'i=STDIN.read; STDOUT.print i+"a"; STDERR.print i+"A"')
- ob,eb,tb = Open3.popen3(RUBY, '-e', 'i=STDIN.read; STDOUT.print i+"b"; STDERR.print i+"B"', STDIN=>oa)
- oc,ec,tc = Open3.popen3(RUBY, '-e', 'i=STDIN.read; STDOUT.print i+"c"; STDERR.print i+"C"', STDIN=>ob)
- od,ed,td = Open3.popen3(RUBY, '-e', 'i=STDIN.read; STDOUT.print i+"d"; STDERR.print i+"D"', STDIN=>eb)
- oe,ee,te = Open3.popen3(RUBY, '-e', 'i=STDIN.read; STDOUT.print i+"e"; STDERR.print i+"E"', STDIN=>ea)
- of,ef,tf = Open3.popen3(RUBY, '-e', 'i=STDIN.read; STDOUT.print i+"f"; STDERR.print i+"F"', STDIN=>oe)
- og,eg,tg = Open3.popen3(RUBY, '-e', 'i=STDIN.read; STDOUT.print i+"g"; STDERR.print i+"G"', STDIN=>ee)
- oa.close
- ea.close
- ob.close
- eb.close
- oe.close
- ee.close
-
- ia.print "0"
- ia.close
- assert_equal("0abc", oc.read)
- assert_equal("0abC", ec.read)
- assert_equal("0aBd", od.read)
- assert_equal("0aBD", ed.read)
- assert_equal("0Aef", of.read)
- assert_equal("0AeF", ef.read)
- assert_equal("0AEg", og.read)
- assert_equal("0AEG", eg.read)
- ensure
- ia.close if !ia.closed?
- oa.close if !oa.closed?
- ea.close if !ea.closed?
- ob.close if !ob.closed?
- eb.close if !eb.closed?
- oc.close if !oc.closed?
- ec.close if !ec.closed?
- od.close if !od.closed?
- ed.close if !ed.closed?
- oe.close if !oe.closed?
- ee.close if !ee.closed?
- of.close if !of.closed?
- ef.close if !ef.closed?
- og.close if !og.closed?
- eg.close if !eg.closed?
+ def test_pipeline_r
+ Open3.pipeline_r([RUBY, '-e', 'print "1"'],
+ [RUBY, '-e', 'print STDIN.read + "2"']) {|o,ts|
+ assert_kind_of(IO, o)
+ assert_kind_of(Array, ts)
+ assert_equal(2, ts.length)
+ ts.each {|t| assert_kind_of(Thread, t) }
+ assert_equal("12", o.read)
+ ts.each {|t|
+ assert(t.value.success?)
+ }
+ }
end
+
+ def test_pipeline_w
+ command = [RUBY, '-e', 's=STDIN.read; print s[1..-1]; exit s[0] == ?t']
+ str = 'ttftff'
+ Open3.pipeline_w(*[command]*str.length) {|i,ts|
+ assert_kind_of(IO, i)
+ assert_kind_of(Array, ts)
+ assert_equal(str.length, ts.length)
+ ts.each {|t| assert_kind_of(Thread, t) }
+ i.print str
+ i.close
+ ts.each_with_index {|t, i|
+ assert_equal(str[i] == ?t, t.value.success?)
+ }
+ }
+ end
+
end