Method: Open3#popen3
- Defined in:
- lib/open3.rb
#popen3(*cmd, &block) ⇒ Object (private)
:call-seq:
Open3.popen3([env, ] command_line, options = {}) -> [stdin, stdout, stderr, wait_thread]
Open3.popen3([env, ] exe_path, *args, options = {}) -> [stdin, stdout, stderr, wait_thread]
Open3.popen3([env, ] command_line, options = {}) {|stdin, stdout, stderr, wait_thread| ... } -> object
Open3.popen3([env, ] exe_path, *args, options = {}) {|stdin, stdout, stderr, wait_thread| ... } -> object
Basically a wrapper for Process.spawn that:
-
Creates a child process, by calling Process.spawn with the given arguments.
-
Creates streams
stdin
,stdout
, andstderr
, which are the standard input, standard output, and standard error streams in the child process. -
Creates thread
wait_thread
that waits for the child process to exit; the thread has methodpid
, which returns the process ID of the child process.
With no block given, returns the array [stdin, stdout, stderr, wait_thread]
. The caller should close each of the three returned streams.
stdin, stdout, stderr, wait_thread = Open3.popen3('echo')
# => [#<IO:fd 8>, #<IO:fd 10>, #<IO:fd 12>, #<Process::Waiter:0x00007f58d5428f58 run>]
stdin.close
stdout.close
stderr.close
wait_thread.pid # => 2210481
wait_thread.value # => #<Process::Status: pid 2210481 exit 0>
With a block given, calls the block with the four variables (three streams and the wait thread) and returns the block’s return value. The caller need not close the streams:
Open3.popen3('echo') do |stdin, stdout, stderr, wait_thread|
p stdin
p stdout
p stderr
p wait_thread
p wait_thread.pid
p wait_thread.value
end
Output:
#<IO:fd 6>
#<IO:fd 7>
#<IO:fd 9>
#<Process::Waiter:0x00007f58d53606e8 sleep>
2211047
#<Process::Status: pid 2211047 exit 0>
Like Process.spawn, this method has potential security vulnerabilities if called with untrusted input; see Command Injection.
Unlike Process.spawn, this method waits for the child process to exit before returning, so the caller need not do so.
If the first argument is a hash, it becomes leading argument env
in the call to Process.spawn; see Execution Environment.
If the last argument is a hash, it becomes trailing argument options
in the call to Process.spawn; see 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.
Argument command_line
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.popen3('if true; then echo "Foo"; fi') {|*args| p args } # Shell reserved word.
Open3.popen3('echo') {|*args| p args } # Built-in.
Open3.popen3('date > date.tmp') {|*args| p args } # Contains meta character.
Output (similar for each call above):
[#<IO:(closed)>, #<IO:(closed)>, #<IO:(closed)>, #<Process::Waiter:0x00007f58d52f28c8 dead>]
The command line may also contain arguments and options for the command:
Open3.popen3('echo "Foo"') { |i, o, e, t| o.gets }
"Foo\n"
Argument exe_path
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.popen3('/usr/bin/date') { |i, o, e, t| o.gets }
# => "Wed Sep 27 02:56:44 PM CDT 2023\n"
Ruby invokes the executable directly, with no shell and no shell expansion:
Open3.popen3('doesnt_exist') { |i, o, e, 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.popen3('echo', 'C #') { |i, o, e, t| o.gets }
# => "C #\n"
Open3.popen3('echo', 'hello', 'world') { |i, o, e, t| o.gets }
# => "hello world\n"
Take care to avoid deadlocks. Output streams stdout
and stderr
have fixed-size buffers, so reading extensively from one but not the other can cause a deadlock when the unread buffer fills. To avoid that, stdout
and stderr
should be read simultaneously (using threads or IO.select).
Related:
-
Open3.popen2: Makes the standard input and standard output streams of the child process available as separate streams, with no access to the standard error stream.
-
Open3.popen2e: Makes the standard input and the merge of the standard output and standard error streams of the child process available as separate streams.
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/open3.rb', line 218 def popen3(*cmd, &block) if Hash === cmd.last opts = cmd.pop.dup else opts = {} end in_r, in_w = IO.pipe opts[:in] = in_r in_w.sync = true out_r, out_w = IO.pipe opts[:out] = out_w err_r, err_w = IO.pipe opts[:err] = err_w popen_run(cmd, opts, [in_r, out_w, err_w], [in_w, out_r, err_r], &block) end |