diff options
-rw-r--r-- | lib/open3.rb | 129 |
1 files changed, 101 insertions, 28 deletions
diff --git a/lib/open3.rb b/lib/open3.rb index c4089dafe6..c24c4cd44e 100644 --- a/lib/open3.rb +++ b/lib/open3.rb @@ -151,6 +151,8 @@ module Open3 # when the unread buffer fills. # To avoid that, +stdout+ and +stderr+ should be read simultaneously # (using threads or IO.select). + # + # Related: Open3.popen2, Open3.popen2e. def popen3(*cmd, &block) if Hash === cmd.last opts = cmd.pop.dup @@ -172,46 +174,117 @@ module Open3 end module_function :popen3 - # Open3.popen2 is similar to Open3.popen3 except that it doesn't create a pipe for - # the standard error stream. + # :call-seq: + # Open3.popen2([env, ] command_line, options = {}) -> [stdin, stdout, wait_thread] + # Open3.popen2([env, ] exe_path, *args, options = {}) -> [stdin, stdout, wait_thread] + # Open3.popen2([env, ] command_line, options = {}) {|stdin, stdout, wait_thread| ... } -> object + # Open3.popen2([env, ] exe_path, *args, options = {}) {|stdin, stdout, wait_thread| ... } -> object # - # Block form: + # Basically a wrapper for Process.spawn that: # - # Open3.popen2([env,] cmd... [, opts]) {|stdin, stdout, wait_thr| - # pid = wait_thr.pid # pid of the started process. - # ... - # exit_status = wait_thr.value # Process::Status object returned. - # } + # - Creates a child process, by calling Process.spawn with the given arguments. + # - Creates streams +stdin+ and +stdout+, + # which are the standard input and standard output streams + # in the child process. + # - Creates thread +wait_thread+ that waits for the child process to exit; + # the thread has method +pid+, which returns the process ID + # of the child process. # - # Non-block form: + # With no block given, returns the array + # <tt>[stdin, stdout, wait_thread]</tt>. + # The caller should close each of the two returned streams. # - # stdin, stdout, wait_thr = Open3.popen2([env,] cmd... [, opts]) - # ... - # stdin.close # stdin and stdout should be closed explicitly in this form. + # stdin, stdout, wait_thread = Open3.popen2('echo') + # # => [#<IO:fd 6>, #<IO:fd 7>, #<Process::Waiter:0x00007f58d52dbe98 run>] + # stdin.close # stdout.close + # wait_thread.pid # => 2263572 + # wait_thread.value # => #<Process::Status: pid 2263572 exit 0> # - # See Process.spawn for the optional hash arguments _env_ and _opts_. + # With a block given, calls the block with the three variables + # (two streams and the wait thread) + # and returns the block's return value. + # The caller need not close the streams: + # + # Open3.popen2('echo') do |stdin, stdout, wait_thread| + # p stdin + # p stdout + # p wait_thread + # p wait_thread.pid + # p wait_thread.value + # end + # + # Output: + # + # #<IO:fd 6> + # #<IO:fd 7> + # #<Process::Waiter:0x00007f58d59a34b0 sleep> + # 2263636 + # #<Process::Status: pid 2263636 exit 0> + # + # Like Process.spawn, this method has potential security vulnerabilities + # if called with untrusted input; + # see {Command Injection}[rdoc-ref:command_injection.rdoc]. + # + # Unlike Process.spawn, this method waits for the child process to exit + # before returning, so the caller need not do so. + # + # Argument +options+ is a hash of options for the new process; + # see {Execution Options}[rdoc-ref:Process@Execution+Options]. + # + # The single required argument is one of the following: + # + # - +command_line+ if it is a string, + # and if it begins with a shell reserved word or special built-in, + # or if it contains one or more metacharacters. + # - +exe_path+ otherwise. + # + # <b>Argument +command_line+</b> + # + # \String argument +command_line+ is a command line to be passed to a shell; + # it must begin with a shell reserved word, begin with a special built-in, + # or contain meta characters: + # + # Open3.popen2('if true; then echo "Foo"; fi') {|*args| p args } # Shell reserved word. + # Open3.popen2('echo') {|*args| p args } # Built-in. + # Open3.popen2('date > date.tmp') {|*args| p args } # Contains meta character. + # + # Output (for each call above): + # + # # => [#<IO:(closed)>, #<IO:(closed)>, #<Process::Waiter:0x00007f7577dfe410 dead>] + # + # The command line may also contain arguments and options for the command: + # + # Open3.popen2('echo "Foo"') { |i, o, t| o.gets } + # "Foo\n" + # + # <b>Argument +exe_path+</b> + # + # Argument +exe_path+ is one of the following: + # + # - The string path to an executable to be called. + # - A 2-element array containing the path to an executable + # and the string to be used as the name of the executing process. # # Example: # - # Open3.popen2("wc -c") {|i,o,t| - # i.print "answer to life the universe and everything" - # i.close - # p o.gets #=> "42\n" - # } + # Open3.popen2('/usr/bin/date') { |i, o, t| o.gets } + # # => "Thu Sep 28 09:41:06 AM CDT 2023\n" # - # Open3.popen2("bc -q") {|i,o,t| - # i.puts "obase=13" - # i.puts "6 * 9" - # p o.gets #=> "42\n" - # } + # Ruby invokes the executable directly, with no shell and no shell expansion: + # + # Open3.popen2('doesnt_exist') { |i, o, t| o.gets } # Raises Errno::ENOENT + # + # If one or more +args+ is given, each is an argument or option + # to be passed to the executable: + # + # Open3.popen2('echo', 'C #') { |i, o, t| o.gets } + # # => "C #\n" + # Open3.popen2('echo', 'hello', 'world') { |i, o, t| o.gets } + # # => "hello world\n" # - # Open3.popen2("dc") {|i,o,t| - # i.print "42P" - # i.close - # p o.read #=> "*" - # } # + # Related: Open3.popen3, Open3.popen2e. def popen2(*cmd, &block) if Hash === cmd.last opts = cmd.pop.dup |