@@ -344,6 +344,131 @@ module Open3
344344 def self?.capture3 : (*String, ?stdin_data: String, ?binmode: boolish) -> [String, String, Process::Status]
345345 | (env, *String, ?stdin_data: String, ?binmode: boolish) -> [String, String, Process::Status]
346346
347+ # <!--
348+ # rdoc-file=lib/open3.rb
349+ # - Open3.popen2([env, ] command_line, options = {}) -> [stdin, stdout, wait_thread]
350+ # - Open3.popen2([env, ] exe_path, *args, options = {}) -> [stdin, stdout, wait_thread]
351+ # - Open3.popen2([env, ] command_line, options = {}) {|stdin, stdout, wait_thread| ... } -> object
352+ # - Open3.popen2([env, ] exe_path, *args, options = {}) {|stdin, stdout, wait_thread| ... } -> object
353+ # -->
354+ # Basically a wrapper for [Process.spawn](rdoc-ref:Process.spawn) that:
355+ #
356+ # * Creates a child process, by calling Process.spawn with the given
357+ # arguments.
358+ # * Creates streams `stdin` and `stdout`, which are the standard input and
359+ # standard output streams in the child process.
360+ # * Creates thread `wait_thread` that waits for the child process to exit; the
361+ # thread has method `pid`, which returns the process ID of the child
362+ # process.
363+ #
364+ # With no block given, returns the array `[stdin, stdout, wait_thread]`. The
365+ # caller should close each of the two returned streams.
366+ #
367+ # stdin, stdout, wait_thread = Open3.popen2('echo')
368+ # # => [#<IO:fd 6>, #<IO:fd 7>, #<Process::Waiter:0x00007f58d52dbe98 run>]
369+ # stdin.close
370+ # stdout.close
371+ # wait_thread.pid # => 2263572
372+ # wait_thread.value # => #<Process::Status: pid 2263572 exit 0>
373+ #
374+ # With a block given, calls the block with the three variables (two streams and
375+ # the wait thread) and returns the block's return value. The caller need not
376+ # close the streams:
377+ #
378+ # Open3.popen2('echo') do |stdin, stdout, wait_thread|
379+ # p stdin
380+ # p stdout
381+ # p wait_thread
382+ # p wait_thread.pid
383+ # p wait_thread.value
384+ # end
385+ #
386+ # Output:
387+ #
388+ # #<IO:fd 6>
389+ # #<IO:fd 7>
390+ # #<Process::Waiter:0x00007f58d59a34b0 sleep>
391+ # 2263636
392+ # #<Process::Status: pid 2263636 exit 0>
393+ #
394+ # Like Process.spawn, this method has potential security vulnerabilities if
395+ # called with untrusted input; see [Command
396+ # Injection](rdoc-ref:command_injection.rdoc@Command+Injection).
397+ #
398+ # Unlike Process.spawn, this method waits for the child process to exit before
399+ # returning, so the caller need not do so.
400+ #
401+ # If the first argument is a hash, it becomes leading argument `env` in the call
402+ # to Process.spawn; see [Execution
403+ # Environment](rdoc-ref:Process@Execution+Environment).
404+ #
405+ # If the last argument is a hash, it becomes trailing argument `options` in the
406+ # call to Process.spawn; see [Execution
407+ # Options](rdoc-ref:Process@Execution+Options).
408+ #
409+ # The single required argument is one of the following:
410+ #
411+ # * `command_line` if it is a string, and if it begins with a shell reserved
412+ # word or special built-in, or if it contains one or more metacharacters.
413+ # * `exe_path` otherwise.
414+ #
415+ # **Argument `command_line`**
416+ #
417+ # String argument `command_line` is a command line to be passed to a shell; it
418+ # must begin with a shell reserved word, begin with a special built-in, or
419+ # contain meta characters:
420+ #
421+ # Open3.popen2('if true; then echo "Foo"; fi') {|*args| p args } # Shell reserved word.
422+ # Open3.popen2('echo') {|*args| p args } # Built-in.
423+ # Open3.popen2('date > date.tmp') {|*args| p args } # Contains meta character.
424+ #
425+ # Output (similar for each call above):
426+ #
427+ # # => [#<IO:(closed)>, #<IO:(closed)>, #<Process::Waiter:0x00007f7577dfe410 dead>]
428+ #
429+ # The command line may also contain arguments and options for the command:
430+ #
431+ # Open3.popen2('echo "Foo"') { |i, o, t| o.gets }
432+ # "Foo\n"
433+ #
434+ # **Argument `exe_path`**
435+ #
436+ # Argument `exe_path` is one of the following:
437+ #
438+ # * The string path to an executable to be called.
439+ # * A 2-element array containing the path to an executable and the string to
440+ # be used as the name of the executing process.
441+ #
442+ # Example:
443+ #
444+ # Open3.popen2('/usr/bin/date') { |i, o, t| o.gets }
445+ # # => "Thu Sep 28 09:41:06 AM CDT 2023\n"
446+ #
447+ # Ruby invokes the executable directly, with no shell and no shell expansion:
448+ #
449+ # Open3.popen2('doesnt_exist') { |i, o, t| o.gets } # Raises Errno::ENOENT
450+ #
451+ # If one or more `args` is given, each is an argument or option to be passed to
452+ # the executable:
453+ #
454+ # Open3.popen2('echo', 'C #') { |i, o, t| o.gets }
455+ # # => "C #\n"
456+ # Open3.popen2('echo', 'hello', 'world') { |i, o, t| o.gets }
457+ # # => "hello world\n"
458+ #
459+ # Related:
460+ #
461+ # * Open3.popen2e: Makes the standard input and the merge of the standard
462+ # output and standard error streams of the child process available as
463+ # separate streams.
464+ # * Open3.popen3: Makes the standard input, standard output, and standard
465+ # error streams of the child process available as separate streams.
466+ #
467+ def self?.popen2 : (*String, ?unsetenv_others: bool , ?pgroup: true | Integer, ?umask: Integer, ?close_others: bool , ?chdir: String) -> [IO, IO, Process::Waiter]
468+ | (env, *String, ?unsetenv_others: bool , ?pgroup: true | Integer, ?umask: Integer, ?close_others: bool , ?chdir: String) -> [IO, IO, Process::Waiter]
469+ | [T] (*String, ?unsetenv_others: bool , ?pgroup: true | Integer, ?umask: Integer, ?close_others: bool , ?chdir: String) { (IO, IO, Process::Waiter) -> T } -> T
470+ | [T] (env, *String, ?unsetenv_others: bool , ?pgroup: true | Integer, ?umask: Integer, ?close_others: bool , ?chdir: String) { (IO, IO, Process::Waiter) -> T } -> T
471+
347472 # <!--
348473 # rdoc-file=lib/open3.rb
349474 # - Open3.popen3([env, ] command_line, options = {}) -> [stdin, stdout, stderr, wait_thread]
0 commit comments