@@ -245,4 +245,138 @@ module Open3
245245 #
246246 def self?.capture2e : (*String, ?stdin_data: String, ?binmode: boolish) -> [String, Process::Status]
247247 | (env, *String, ?stdin_data: String, ?binmode: boolish) -> [String, Process::Status]
248+
249+ # <!--
250+ # rdoc-file=lib/open3.rb
251+ # - Open3.popen3([env, ] command_line, options = {}) -> [stdin, stdout, stderr, wait_thread]
252+ # - Open3.popen3([env, ] exe_path, *args, options = {}) -> [stdin, stdout, stderr, wait_thread]
253+ # - Open3.popen3([env, ] command_line, options = {}) {|stdin, stdout, stderr, wait_thread| ... } -> object
254+ # - Open3.popen3([env, ] exe_path, *args, options = {}) {|stdin, stdout, stderr, wait_thread| ... } -> object
255+ # -->
256+ # Basically a wrapper for [Process.spawn](rdoc-ref:Process.spawn) that:
257+ #
258+ # * Creates a child process, by calling Process.spawn with the given
259+ # arguments.
260+ # * Creates streams `stdin`, `stdout`, and `stderr`, which are the standard
261+ # input, standard output, and standard error streams in the child process.
262+ # * Creates thread `wait_thread` that waits for the child process to exit; the
263+ # thread has method `pid`, which returns the process ID of the child
264+ # process.
265+ #
266+ # With no block given, returns the array `[stdin, stdout, stderr, wait_thread]`.
267+ # The caller should close each of the three returned streams.
268+ #
269+ # stdin, stdout, stderr, wait_thread = Open3.popen3('echo')
270+ # # => [#<IO:fd 8>, #<IO:fd 10>, #<IO:fd 12>, #<Process::Waiter:0x00007f58d5428f58 run>]
271+ # stdin.close
272+ # stdout.close
273+ # stderr.close
274+ # wait_thread.pid # => 2210481
275+ # wait_thread.value # => #<Process::Status: pid 2210481 exit 0>
276+ #
277+ # With a block given, calls the block with the four variables (three streams and
278+ # the wait thread) and returns the block's return value. The caller need not
279+ # close the streams:
280+ #
281+ # Open3.popen3('echo') do |stdin, stdout, stderr, wait_thread|
282+ # p stdin
283+ # p stdout
284+ # p stderr
285+ # p wait_thread
286+ # p wait_thread.pid
287+ # p wait_thread.value
288+ # end
289+ #
290+ # Output:
291+ #
292+ # #<IO:fd 6>
293+ # #<IO:fd 7>
294+ # #<IO:fd 9>
295+ # #<Process::Waiter:0x00007f58d53606e8 sleep>
296+ # 2211047
297+ # #<Process::Status: pid 2211047 exit 0>
298+ #
299+ # Like Process.spawn, this method has potential security vulnerabilities if
300+ # called with untrusted input; see [Command
301+ # Injection](rdoc-ref:command_injection.rdoc@Command+Injection).
302+ #
303+ # Unlike Process.spawn, this method waits for the child process to exit before
304+ # returning, so the caller need not do so.
305+ #
306+ # If the first argument is a hash, it becomes leading argument `env` in the call
307+ # to Process.spawn; see [Execution
308+ # Environment](rdoc-ref:Process@Execution+Environment).
309+ #
310+ # If the last argument is a hash, it becomes trailing argument `options` in the
311+ # call to Process.spawn; see [Execution
312+ # Options](rdoc-ref:Process@Execution+Options).
313+ #
314+ # The single required argument is one of the following:
315+ #
316+ # * `command_line` if it is a string, and if it begins with a shell reserved
317+ # word or special built-in, or if it contains one or more metacharacters.
318+ # * `exe_path` otherwise.
319+ #
320+ # **Argument `command_line`**
321+ #
322+ # String argument `command_line` is a command line to be passed to a shell; it
323+ # must begin with a shell reserved word, begin with a special built-in, or
324+ # contain meta characters:
325+ #
326+ # Open3.popen3('if true; then echo "Foo"; fi') {|*args| p args } # Shell reserved word.
327+ # Open3.popen3('echo') {|*args| p args } # Built-in.
328+ # Open3.popen3('date > date.tmp') {|*args| p args } # Contains meta character.
329+ #
330+ # Output (similar for each call above):
331+ #
332+ # [#<IO:(closed)>, #<IO:(closed)>, #<IO:(closed)>, #<Process::Waiter:0x00007f58d52f28c8 dead>]
333+ #
334+ # The command line may also contain arguments and options for the command:
335+ #
336+ # Open3.popen3('echo "Foo"') { |i, o, e, t| o.gets }
337+ # "Foo\n"
338+ #
339+ # **Argument `exe_path`**
340+ #
341+ # Argument `exe_path` is one of the following:
342+ #
343+ # * The string path to an executable to be called.
344+ # * A 2-element array containing the path to an executable and the string to
345+ # be used as the name of the executing process.
346+ #
347+ # Example:
348+ #
349+ # Open3.popen3('/usr/bin/date') { |i, o, e, t| o.gets }
350+ # # => "Wed Sep 27 02:56:44 PM CDT 2023\n"
351+ #
352+ # Ruby invokes the executable directly, with no shell and no shell expansion:
353+ #
354+ # Open3.popen3('doesnt_exist') { |i, o, e, t| o.gets } # Raises Errno::ENOENT
355+ #
356+ # If one or more `args` is given, each is an argument or option to be passed to
357+ # the executable:
358+ #
359+ # Open3.popen3('echo', 'C #') { |i, o, e, t| o.gets }
360+ # # => "C #\n"
361+ # Open3.popen3('echo', 'hello', 'world') { |i, o, e, t| o.gets }
362+ # # => "hello world\n"
363+ #
364+ # Take care to avoid deadlocks. Output streams `stdout` and `stderr` have
365+ # fixed-size buffers, so reading extensively from one but not the other can
366+ # cause a deadlock when the unread buffer fills. To avoid that, `stdout` and
367+ # `stderr` should be read simultaneously (using threads or IO.select).
368+ #
369+ # Related:
370+ #
371+ # * Open3.popen2: Makes the standard input and standard output streams of the
372+ # child process available as separate streams, with no access to the
373+ # standard error stream.
374+ # * Open3.popen2e: Makes the standard input and the merge of the standard
375+ # output and standard error streams of the child process available as
376+ # separate streams.
377+ #
378+ def self?.popen3 : (*String, ?unsetenv_others: bool , ?pgroup: true | Integer, ?umask: Integer, ?close_others: bool , ?chdir: String) -> [IO, IO, IO, Process::Waiter]
379+ | (env, *String, ?unsetenv_others: bool , ?pgroup: true | Integer, ?umask: Integer, ?close_others: bool , ?chdir: String) -> [IO, IO, IO, Process::Waiter]
380+ | [T] (*String, ?unsetenv_others: bool , ?pgroup: true | Integer, ?umask: Integer, ?close_others: bool , ?chdir: String) { (IO, IO, IO, Process::Waiter) -> T } -> T
381+ | [T] (env, *String, ?unsetenv_others: bool , ?pgroup: true | Integer, ?umask: Integer, ?close_others: bool , ?chdir: String) { (IO, IO, IO, Process::Waiter) -> T } -> T
248382end
0 commit comments