open3 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b3ab5dfcb825c74af61129ee82254609322af777421f454eb90053875bdbc851
4
- data.tar.gz: fdf3f7c0783de3763390a6e62a7ec984f262d61ca5d8906e186ad7e7deeff5d2
3
+ metadata.gz: d381ba178ac3fc0539b0f526890e2951b8e6f0332d294cd4930b895837643b3b
4
+ data.tar.gz: 7435b46d29c7e24231af4adb8106a32cde9850a896e61153a08fe4b519d5645b
5
5
  SHA512:
6
- metadata.gz: 3e3508856456efd5c0700447c9e122163ea490b32a7bd4cc09d2d4fa79639b53baac5b8f45d983e5185dd439c93cfba2b546c302569e1dccf6d4086c0a3b0858
7
- data.tar.gz: a79fd57d1a714b793ecc3849231cf3d58fd07bcc962ffad144b98ec7443d8da1642e97ab10e6f251c720cc9d0cfe72a809fb71c0c915226fcbed0b2a1c607839
6
+ metadata.gz: e8770606eb547891a3c87fd8636c7a911189d940aa332909098fff948660978c717bb3bd2727fb13378e6b10ee207b7cee17f91a1b02ef18a20cded2bf76f3a9
7
+ data.tar.gz: b12409782b071af20bc628579d5a03ea89023c5e3dd1acbae90a67bafbb748d5c7a4de46ecc5d9ddc2da020de5ff53b89448719025706fc08dfe33b87563df83
@@ -11,7 +11,7 @@ jobs:
11
11
  os: [ ubuntu-latest, macos-latest ]
12
12
  runs-on: ${{ matrix.os }}
13
13
  steps:
14
- - uses: actions/checkout@v3
14
+ - uses: actions/checkout@v4
15
15
  - name: Set up Ruby
16
16
  uses: ruby/setup-ruby@v1
17
17
  with:
@@ -3,15 +3,22 @@ name: test
3
3
  on: [push, pull_request]
4
4
 
5
5
  jobs:
6
- build:
6
+ ruby-versions:
7
+ uses: ruby/actions/.github/workflows/ruby_versions.yml@master
8
+ with:
9
+ engine: cruby
10
+ min_version: 2.6
11
+
12
+ test:
13
+ needs: ruby-versions
7
14
  name: build (${{ matrix.ruby }} / ${{ matrix.os }})
8
15
  strategy:
9
16
  matrix:
10
- ruby: [ '3.0', 2.7, 2.6, head ]
17
+ ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }}
11
18
  os: [ ubuntu-latest, macos-latest ]
12
19
  runs-on: ${{ matrix.os }}
13
20
  steps:
14
- - uses: actions/checkout@v3
21
+ - uses: actions/checkout@v4
15
22
  - name: Set up Ruby
16
23
  uses: ruby/setup-ruby@v1
17
24
  with:
data/Gemfile CHANGED
@@ -6,4 +6,5 @@ group :development do
6
6
  gem "bundler"
7
7
  gem "rake"
8
8
  gem "test-unit"
9
+ gem "test-unit-ruby-core"
9
10
  end
data/Rakefile CHANGED
@@ -7,11 +7,4 @@ Rake::TestTask.new(:test) do |t|
7
7
  t.test_files = FileList["test/**/test_*.rb"]
8
8
  end
9
9
 
10
- task :sync_tool do
11
- require 'fileutils'
12
- FileUtils.cp "../ruby/tool/lib/core_assertions.rb", "./test/lib"
13
- FileUtils.cp "../ruby/tool/lib/envutil.rb", "./test/lib"
14
- FileUtils.cp "../ruby/tool/lib/find_executable.rb", "./test/lib"
15
- end
16
-
17
10
  task :default => :test
data/lib/open3/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Open3
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/open3.rb CHANGED
@@ -33,55 +33,133 @@ require 'open3/version'
33
33
 
34
34
  module Open3
35
35
 
36
- # Open stdin, stdout, and stderr streams and start external executable.
37
- # In addition, a thread to wait for the started process is created.
38
- # The thread has a pid method and a thread variable :pid which is the pid of
39
- # the started process.
36
+ # :call-seq:
37
+ # Open3.popen3([env, ] command_line, options = {}) -> [stdin, stdout, stderr, wait_thread]
38
+ # Open3.popen3([env, ] exe_path, *args, options = {}) -> [stdin, stdout, stderr, wait_thread]
39
+ # Open3.popen3([env, ] command_line, options = {}) {|stdin, stdout, stderr, wait_thread| ... } -> object
40
+ # Open3.popen3([env, ] exe_path, *args, options = {}) {|stdin, stdout, stderr, wait_thread| ... } -> object
41
+ #
42
+ # Basically a wrapper for Process.spawn that:
43
+ #
44
+ # - Creates a child process, by calling Process.spawn with the given arguments.
45
+ # - Creates streams +stdin+, +stdout+, and +stderr+,
46
+ # which are the standard input, standard output, and standard error streams
47
+ # in the child process.
48
+ # - Creates thread +wait_thread+ that waits for the child process to exit;
49
+ # the thread has method +pid+, which returns the process ID
50
+ # of the child process.
51
+ #
52
+ # With no block given, returns the array
53
+ # <tt>[stdin, stdout, stderr, wait_thread]</tt>.
54
+ # The caller should close each of the three returned streams.
55
+ #
56
+ # stdin, stdout, stderr, wait_thread = Open3.popen3('echo')
57
+ # # => [#<IO:fd 8>, #<IO:fd 10>, #<IO:fd 12>, #<Process::Waiter:0x00007f58d5428f58 run>]
58
+ # stdin.close
59
+ # stdout.close
60
+ # stderr.close
61
+ # wait_thread.pid # => 2210481
62
+ # wait_thread.value # => #<Process::Status: pid 2210481 exit 0>
63
+ #
64
+ # With a block given, calls the block with the four variables
65
+ # (three streams and the wait thread)
66
+ # and returns the block's return value.
67
+ # The caller need not close the streams:
68
+ #
69
+ # Open3.popen3('echo') do |stdin, stdout, stderr, wait_thread|
70
+ # p stdin
71
+ # p stdout
72
+ # p stderr
73
+ # p wait_thread
74
+ # p wait_thread.pid
75
+ # p wait_thread.value
76
+ # end
40
77
  #
41
- # Block form:
78
+ # Output:
42
79
  #
43
- # Open3.popen3([env,] cmd... [, opts]) {|stdin, stdout, stderr, wait_thr|
44
- # pid = wait_thr.pid # pid of the started process.
45
- # ...
46
- # exit_status = wait_thr.value # Process::Status object returned.
47
- # }
80
+ # #<IO:fd 6>
81
+ # #<IO:fd 7>
82
+ # #<IO:fd 9>
83
+ # #<Process::Waiter:0x00007f58d53606e8 sleep>
84
+ # 2211047
85
+ # #<Process::Status: pid 2211047 exit 0>
48
86
  #
49
- # Non-block form:
87
+ # Like Process.spawn, this method has potential security vulnerabilities
88
+ # if called with untrusted input;
89
+ # see {Command Injection}[rdoc-ref:command_injection.rdoc].
50
90
  #
51
- # stdin, stdout, stderr, wait_thr = Open3.popen3([env,] cmd... [, opts])
52
- # pid = wait_thr[:pid] # pid of the started process
53
- # ...
54
- # stdin.close # stdin, stdout and stderr should be closed explicitly in this form.
55
- # stdout.close
56
- # stderr.close
57
- # exit_status = wait_thr.value # Process::Status object returned.
91
+ # Unlike Process.spawn, this method waits for the child process to exit
92
+ # before returning, so the caller need not do so.
58
93
  #
59
- # The parameters env, cmd, and opts are passed to Process.spawn.
60
- # A commandline string and a list of argument strings can be accepted as follows:
94
+ # Argument +options+ is a hash of options for the new process;
95
+ # see {Execution Options}[rdoc-ref:Process@Execution+Options].
61
96
  #
62
- # Open3.popen3("echo abc") {|i, o, e, t| ... }
63
- # Open3.popen3("echo", "abc") {|i, o, e, t| ... }
64
- # Open3.popen3(["echo", "argv0"], "abc") {|i, o, e, t| ... }
97
+ # The single required argument is one of the following:
65
98
  #
66
- # If the last parameter, opts, is a Hash, it is recognized as an option for Process.spawn.
99
+ # - +command_line+ if it is a string,
100
+ # and if it begins with a shell reserved word or special built-in,
101
+ # or if it contains one or more metacharacters.
102
+ # - +exe_path+ otherwise.
67
103
  #
68
- # Open3.popen3("pwd", :chdir=>"/") {|i,o,e,t|
69
- # p o.read.chomp #=> "/"
70
- # }
104
+ # <b>Argument +command_line+</b>
105
+ #
106
+ # \String argument +command_line+ is a command line to be passed to a shell;
107
+ # it must begin with a shell reserved word, begin with a special built-in,
108
+ # or contain meta characters:
109
+ #
110
+ # Open3.popen3('if true; then echo "Foo"; fi') {|*args| p args } # Shell reserved word.
111
+ # Open3.popen3('echo') {|*args| p args } # Built-in.
112
+ # Open3.popen3('date > date.tmp') {|*args| p args } # Contains meta character.
113
+ #
114
+ # Output (similar for each call above):
115
+ #
116
+ # [#<IO:(closed)>, #<IO:(closed)>, #<IO:(closed)>, #<Process::Waiter:0x00007f58d52f28c8 dead>]
117
+ #
118
+ # The command line may also contain arguments and options for the command:
119
+ #
120
+ # Open3.popen3('echo "Foo"') { |i, o, e, t| o.gets }
121
+ # "Foo\n"
122
+ #
123
+ # <b>Argument +exe_path+</b>
124
+ #
125
+ # Argument +exe_path+ is one of the following:
126
+ #
127
+ # - The string path to an executable to be called.
128
+ # - A 2-element array containing the path to an executable
129
+ # and the string to be used as the name of the executing process.
130
+ #
131
+ # Example:
132
+ #
133
+ # Open3.popen3('/usr/bin/date') { |i, o, e, t| o.gets }
134
+ # # => "Wed Sep 27 02:56:44 PM CDT 2023\n"
135
+ #
136
+ # Ruby invokes the executable directly, with no shell and no shell expansion:
137
+ #
138
+ # Open3.popen3('doesnt_exist') { |i, o, e, t| o.gets } # Raises Errno::ENOENT
139
+ #
140
+ # If one or more +args+ is given, each is an argument or option
141
+ # to be passed to the executable:
71
142
  #
72
- # wait_thr.value waits for the termination of the process.
73
- # The block form also waits for the process when it returns.
143
+ # Open3.popen3('echo', 'C #') { |i, o, e, t| o.gets }
144
+ # # => "C #\n"
145
+ # Open3.popen3('echo', 'hello', 'world') { |i, o, e, t| o.gets }
146
+ # # => "hello world\n"
74
147
  #
75
- # Closing stdin, stdout and stderr does not wait for the process to complete.
148
+ # Take care to avoid deadlocks.
149
+ # Output streams +stdout+ and +stderr+ have fixed-size buffers,
150
+ # so reading extensively from one but not the other can cause a deadlock
151
+ # when the unread buffer fills.
152
+ # To avoid that, +stdout+ and +stderr+ should be read simultaneously
153
+ # (using threads or IO.select).
76
154
  #
77
- # You should be careful to avoid deadlocks.
78
- # Since pipes are fixed length buffers,
79
- # Open3.popen3("prog") {|i, o, e, t| o.read } deadlocks if
80
- # the program generates too much output on stderr.
81
- # You should read stdout and stderr simultaneously (using threads or IO.select).
82
- # However, if you don't need stderr output, you can use Open3.popen2.
83
- # If merged stdout and stderr output is not a problem, you can use Open3.popen2e.
84
- # If you really need stdout and stderr output as separate strings, you can consider Open3.capture3.
155
+ # Related:
156
+ #
157
+ # - Open3.popen2: Makes the standard input and standard output streams
158
+ # of the child process available as separate streams,
159
+ # with no access to the standard error stream.
160
+ # - Open3.popen2e: Makes the standard input and the merge
161
+ # of the standard output and standard error streams
162
+ # of the child process available as separate streams.
85
163
  #
86
164
  def popen3(*cmd, &block)
87
165
  if Hash === cmd.last
@@ -104,45 +182,124 @@ module Open3
104
182
  end
105
183
  module_function :popen3
106
184
 
107
- # Open3.popen2 is similar to Open3.popen3 except that it doesn't create a pipe for
108
- # the standard error stream.
185
+ # :call-seq:
186
+ # Open3.popen2([env, ] command_line, options = {}) -> [stdin, stdout, wait_thread]
187
+ # Open3.popen2([env, ] exe_path, *args, options = {}) -> [stdin, stdout, wait_thread]
188
+ # Open3.popen2([env, ] command_line, options = {}) {|stdin, stdout, wait_thread| ... } -> object
189
+ # Open3.popen2([env, ] exe_path, *args, options = {}) {|stdin, stdout, wait_thread| ... } -> object
190
+ #
191
+ # Basically a wrapper for Process.spawn that:
192
+ #
193
+ # - Creates a child process, by calling Process.spawn with the given arguments.
194
+ # - Creates streams +stdin+ and +stdout+,
195
+ # which are the standard input and standard output streams
196
+ # in the child process.
197
+ # - Creates thread +wait_thread+ that waits for the child process to exit;
198
+ # the thread has method +pid+, which returns the process ID
199
+ # of the child process.
200
+ #
201
+ # With no block given, returns the array
202
+ # <tt>[stdin, stdout, wait_thread]</tt>.
203
+ # The caller should close each of the two returned streams.
204
+ #
205
+ # stdin, stdout, wait_thread = Open3.popen2('echo')
206
+ # # => [#<IO:fd 6>, #<IO:fd 7>, #<Process::Waiter:0x00007f58d52dbe98 run>]
207
+ # stdin.close
208
+ # stdout.close
209
+ # wait_thread.pid # => 2263572
210
+ # wait_thread.value # => #<Process::Status: pid 2263572 exit 0>
211
+ #
212
+ # With a block given, calls the block with the three variables
213
+ # (two streams and the wait thread)
214
+ # and returns the block's return value.
215
+ # The caller need not close the streams:
216
+ #
217
+ # Open3.popen2('echo') do |stdin, stdout, wait_thread|
218
+ # p stdin
219
+ # p stdout
220
+ # p wait_thread
221
+ # p wait_thread.pid
222
+ # p wait_thread.value
223
+ # end
109
224
  #
110
- # Block form:
225
+ # Output:
111
226
  #
112
- # Open3.popen2([env,] cmd... [, opts]) {|stdin, stdout, wait_thr|
113
- # pid = wait_thr.pid # pid of the started process.
114
- # ...
115
- # exit_status = wait_thr.value # Process::Status object returned.
116
- # }
227
+ # #<IO:fd 6>
228
+ # #<IO:fd 7>
229
+ # #<Process::Waiter:0x00007f58d59a34b0 sleep>
230
+ # 2263636
231
+ # #<Process::Status: pid 2263636 exit 0>
117
232
  #
118
- # Non-block form:
233
+ # Like Process.spawn, this method has potential security vulnerabilities
234
+ # if called with untrusted input;
235
+ # see {Command Injection}[rdoc-ref:command_injection.rdoc].
119
236
  #
120
- # stdin, stdout, wait_thr = Open3.popen2([env,] cmd... [, opts])
121
- # ...
122
- # stdin.close # stdin and stdout should be closed explicitly in this form.
123
- # stdout.close
237
+ # Unlike Process.spawn, this method waits for the child process to exit
238
+ # before returning, so the caller need not do so.
239
+ #
240
+ # Argument +options+ is a hash of options for the new process;
241
+ # see {Execution Options}[rdoc-ref:Process@Execution+Options].
242
+ #
243
+ # The single required argument is one of the following:
244
+ #
245
+ # - +command_line+ if it is a string,
246
+ # and if it begins with a shell reserved word or special built-in,
247
+ # or if it contains one or more metacharacters.
248
+ # - +exe_path+ otherwise.
249
+ #
250
+ # <b>Argument +command_line+</b>
251
+ #
252
+ # \String argument +command_line+ is a command line to be passed to a shell;
253
+ # it must begin with a shell reserved word, begin with a special built-in,
254
+ # or contain meta characters:
255
+ #
256
+ # Open3.popen2('if true; then echo "Foo"; fi') {|*args| p args } # Shell reserved word.
257
+ # Open3.popen2('echo') {|*args| p args } # Built-in.
258
+ # Open3.popen2('date > date.tmp') {|*args| p args } # Contains meta character.
259
+ #
260
+ # Output (similar for each call above):
124
261
  #
125
- # See Process.spawn for the optional hash arguments _env_ and _opts_.
262
+ # # => [#<IO:(closed)>, #<IO:(closed)>, #<Process::Waiter:0x00007f7577dfe410 dead>]
263
+ #
264
+ # The command line may also contain arguments and options for the command:
265
+ #
266
+ # Open3.popen2('echo "Foo"') { |i, o, t| o.gets }
267
+ # "Foo\n"
268
+ #
269
+ # <b>Argument +exe_path+</b>
270
+ #
271
+ # Argument +exe_path+ is one of the following:
272
+ #
273
+ # - The string path to an executable to be called.
274
+ # - A 2-element array containing the path to an executable
275
+ # and the string to be used as the name of the executing process.
126
276
  #
127
277
  # Example:
128
278
  #
129
- # Open3.popen2("wc -c") {|i,o,t|
130
- # i.print "answer to life the universe and everything"
131
- # i.close
132
- # p o.gets #=> "42\n"
133
- # }
279
+ # Open3.popen2('/usr/bin/date') { |i, o, t| o.gets }
280
+ # # => "Thu Sep 28 09:41:06 AM CDT 2023\n"
134
281
  #
135
- # Open3.popen2("bc -q") {|i,o,t|
136
- # i.puts "obase=13"
137
- # i.puts "6 * 9"
138
- # p o.gets #=> "42\n"
139
- # }
282
+ # Ruby invokes the executable directly, with no shell and no shell expansion:
283
+ #
284
+ # Open3.popen2('doesnt_exist') { |i, o, t| o.gets } # Raises Errno::ENOENT
285
+ #
286
+ # If one or more +args+ is given, each is an argument or option
287
+ # to be passed to the executable:
288
+ #
289
+ # Open3.popen2('echo', 'C #') { |i, o, t| o.gets }
290
+ # # => "C #\n"
291
+ # Open3.popen2('echo', 'hello', 'world') { |i, o, t| o.gets }
292
+ # # => "hello world\n"
140
293
  #
141
- # Open3.popen2("dc") {|i,o,t|
142
- # i.print "42P"
143
- # i.close
144
- # p o.read #=> "*"
145
- # }
294
+ #
295
+ # Related:
296
+ #
297
+ # - Open3.popen2e: Makes the standard input and the merge
298
+ # of the standard output and standard error streams
299
+ # of the child process available as separate streams.
300
+ # - Open3.popen3: Makes the standard input, standard output,
301
+ # and standard error streams
302
+ # of the child process available as separate streams.
146
303
  #
147
304
  def popen2(*cmd, &block)
148
305
  if Hash === cmd.last
@@ -162,36 +319,123 @@ module Open3
162
319
  end
163
320
  module_function :popen2
164
321
 
165
- # Open3.popen2e is similar to Open3.popen3 except that it merges
166
- # the standard output stream and the standard error stream.
322
+ # :call-seq:
323
+ # Open3.popen2e([env, ] command_line, options = {}) -> [stdin, stdout_and_stderr, wait_thread]
324
+ # Open3.popen2e([env, ] exe_path, *args, options = {}) -> [stdin, stdout_and_stderr, wait_thread]
325
+ # Open3.popen2e([env, ] command_line, options = {}) {|stdin, stdout_and_stderr, wait_thread| ... } -> object
326
+ # Open3.popen2e([env, ] exe_path, *args, options = {}) {|stdin, stdout_and_stderr, wait_thread| ... } -> object
327
+ #
328
+ # Basically a wrapper for Process.spawn that:
329
+ #
330
+ # - Creates a child process, by calling Process.spawn with the given arguments.
331
+ # - Creates streams +stdin+, +stdout_and_stderr+,
332
+ # which are the standard input and the merge of the standard output
333
+ # and standard error streams in the child process.
334
+ # - Creates thread +wait_thread+ that waits for the child process to exit;
335
+ # the thread has method +pid+, which returns the process ID
336
+ # of the child process.
337
+ #
338
+ # With no block given, returns the array
339
+ # <tt>[stdin, stdout_and_stderr, wait_thread]</tt>.
340
+ # The caller should close each of the two returned streams.
341
+ #
342
+ # stdin, stdout_and_stderr, wait_thread = Open3.popen2e('echo')
343
+ # # => [#<IO:fd 6>, #<IO:fd 7>, #<Process::Waiter:0x00007f7577da4398 run>]
344
+ # stdin.close
345
+ # stdout_and_stderr.close
346
+ # wait_thread.pid # => 2274600
347
+ # wait_thread.value # => #<Process::Status: pid 2274600 exit 0>
348
+ #
349
+ # With a block given, calls the block with the three variables
350
+ # (two streams and the wait thread)
351
+ # and returns the block's return value.
352
+ # The caller need not close the streams:
353
+ #
354
+ # Open3.popen2e('echo') do |stdin, stdout_and_stderr, wait_thread|
355
+ # p stdin
356
+ # p stdout_and_stderr
357
+ # p wait_thread
358
+ # p wait_thread.pid
359
+ # p wait_thread.value
360
+ # end
167
361
  #
168
- # Block form:
362
+ # Output:
169
363
  #
170
- # Open3.popen2e([env,] cmd... [, opts]) {|stdin, stdout_and_stderr, wait_thr|
171
- # pid = wait_thr.pid # pid of the started process.
172
- # ...
173
- # exit_status = wait_thr.value # Process::Status object returned.
174
- # }
364
+ # #<IO:fd 6>
365
+ # #<IO:fd 7>
366
+ # #<Process::Waiter:0x00007f75777578c8 sleep>
367
+ # 2274763
368
+ # #<Process::Status: pid 2274763 exit 0>
175
369
  #
176
- # Non-block form:
370
+ # Like Process.spawn, this method has potential security vulnerabilities
371
+ # if called with untrusted input;
372
+ # see {Command Injection}[rdoc-ref:command_injection.rdoc].
177
373
  #
178
- # stdin, stdout_and_stderr, wait_thr = Open3.popen2e([env,] cmd... [, opts])
179
- # ...
180
- # stdin.close # stdin and stdout_and_stderr should be closed explicitly in this form.
181
- # stdout_and_stderr.close
374
+ # Unlike Process.spawn, this method waits for the child process to exit
375
+ # before returning, so the caller need not do so.
376
+ #
377
+ # Argument +options+ is a hash of options for the new process;
378
+ # see {Execution Options}[rdoc-ref:Process@Execution+Options].
379
+ #
380
+ # The single required argument is one of the following:
381
+ #
382
+ # - +command_line+ if it is a string,
383
+ # and if it begins with a shell reserved word or special built-in,
384
+ # or if it contains one or more metacharacters.
385
+ # - +exe_path+ otherwise.
386
+ #
387
+ # <b>Argument +command_line+</b>
388
+ #
389
+ # \String argument +command_line+ is a command line to be passed to a shell;
390
+ # it must begin with a shell reserved word, begin with a special built-in,
391
+ # or contain meta characters:
182
392
  #
183
- # See Process.spawn for the optional hash arguments _env_ and _opts_.
393
+ # Open3.popen2e('if true; then echo "Foo"; fi') {|*args| p args } # Shell reserved word.
394
+ # Open3.popen2e('echo') {|*args| p args } # Built-in.
395
+ # Open3.popen2e('date > date.tmp') {|*args| p args } # Contains meta character.
396
+ #
397
+ # Output (similar for each call above):
398
+ #
399
+ # # => [#<IO:(closed)>, #<IO:(closed)>, #<Process::Waiter:0x00007f7577d8a1f0 dead>]
400
+ #
401
+ # The command line may also contain arguments and options for the command:
402
+ #
403
+ # Open3.popen2e('echo "Foo"') { |i, o_and_e, t| o_and_e.gets }
404
+ # "Foo\n"
405
+ #
406
+ # <b>Argument +exe_path+</b>
407
+ #
408
+ # Argument +exe_path+ is one of the following:
409
+ #
410
+ # - The string path to an executable to be called.
411
+ # - A 2-element array containing the path to an executable
412
+ # and the string to be used as the name of the executing process.
184
413
  #
185
414
  # Example:
186
- # # check gcc warnings
187
- # source = "foo.c"
188
- # Open3.popen2e("gcc", "-Wall", source) {|i,oe,t|
189
- # oe.each {|line|
190
- # if /warning/ =~ line
191
- # ...
192
- # end
193
- # }
194
- # }
415
+ #
416
+ # Open3.popen2e('/usr/bin/date') { |i, o_and_e, t| o_and_e.gets }
417
+ # # => "Thu Sep 28 01:58:45 PM CDT 2023\n"
418
+ #
419
+ # Ruby invokes the executable directly, with no shell and no shell expansion:
420
+ #
421
+ # Open3.popen2e('doesnt_exist') { |i, o_and_e, t| o_and_e.gets } # Raises Errno::ENOENT
422
+ #
423
+ # If one or more +args+ is given, each is an argument or option
424
+ # to be passed to the executable:
425
+ #
426
+ # Open3.popen2e('echo', 'C #') { |i, o_and_e, t| o_and_e.gets }
427
+ # # => "C #\n"
428
+ # Open3.popen2e('echo', 'hello', 'world') { |i, o_and_e, t| o_and_e.gets }
429
+ # # => "hello world\n"
430
+ #
431
+ # Related:
432
+ #
433
+ # - Open3.popen2: Makes the standard input and standard output streams
434
+ # of the child process available as separate streams,
435
+ # with no access to the standard error stream.
436
+ # - Open3.popen3: Makes the standard input, standard output,
437
+ # and standard error streams
438
+ # of the child process available as separate streams.
195
439
  #
196
440
  def popen2e(*cmd, &block)
197
441
  if Hash === cmd.last
@@ -238,44 +482,95 @@ module Open3
238
482
  private :popen_run
239
483
  end
240
484
 
241
- # Open3.capture3 captures the standard output and the standard error of a command.
485
+ # :call-seq:
486
+ # Open3.capture3([env, ] command_line, options = {}) -> [stdout_s, stderr_s, status]
487
+ # Open3.capture3([env, ] exe_path, *args, options = {}) -> [stdout_s, stderr_s, status]
242
488
  #
243
- # stdout_str, stderr_str, status = Open3.capture3([env,] cmd... [, opts])
489
+ # Basically a wrapper for Open3.popen3 that:
244
490
  #
245
- # The arguments env, cmd and opts are passed to Open3.popen3 except
246
- # <code>opts[:stdin_data]</code> and <code>opts[:binmode]</code>. See Process.spawn.
491
+ # - Creates a child process, by calling Open3.popen3 with the given arguments
492
+ # (except for certain entries in hash +options+; see below).
493
+ # - Returns as strings +stdout_s+ and +stderr_s+ the standard output
494
+ # and standard error of the child process.
495
+ # - Returns as +status+ a <tt>Process::Status</tt> object
496
+ # that represents the exit status of the child process.
247
497
  #
248
- # If <code>opts[:stdin_data]</code> is specified, it is sent to the command's standard input.
498
+ # Returns the array <tt>[stdout_s, stderr_s, status]</tt>:
249
499
  #
250
- # If <code>opts[:binmode]</code> is true, internal pipes are set to binary mode.
500
+ # stdout_s, stderr_s, status = Open3.capture3('echo "Foo"')
501
+ # # => ["Foo\n", "", #<Process::Status: pid 2281954 exit 0>]
251
502
  #
252
- # Examples:
503
+ # Like Process.spawn, this method has potential security vulnerabilities
504
+ # if called with untrusted input;
505
+ # see {Command Injection}[rdoc-ref:command_injection.rdoc].
253
506
  #
254
- # # dot is a command of graphviz.
255
- # graph = <<'End'
256
- # digraph g {
257
- # a -> b
258
- # }
259
- # End
260
- # drawn_graph, dot_log = Open3.capture3("dot -v", :stdin_data=>graph)
261
- #
262
- # o, e, s = Open3.capture3("echo abc; sort >&2", :stdin_data=>"foo\nbar\nbaz\n")
263
- # p o #=> "abc\n"
264
- # p e #=> "bar\nbaz\nfoo\n"
265
- # p s #=> #<Process::Status: pid 32682 exit 0>
266
- #
267
- # # generate a thumbnail image using the convert command of ImageMagick.
268
- # # However, if the image is really stored in a file,
269
- # # system("convert", "-thumbnail", "80", "png:#{filename}", "png:-") is better
270
- # # because of reduced memory consumption.
271
- # # But if the image is stored in a DB or generated by the gnuplot Open3.capture2 example,
272
- # # Open3.capture3 should be considered.
273
- # #
274
- # image = File.read("/usr/share/openclipart/png/animals/mammals/sheep-md-v0.1.png", :binmode=>true)
275
- # thumbnail, err, s = Open3.capture3("convert -thumbnail 80 png:- png:-", :stdin_data=>image, :binmode=>true)
276
- # if s.success?
277
- # STDOUT.binmode; print thumbnail
278
- # end
507
+ # Unlike Process.spawn, this method waits for the child process to exit
508
+ # before returning, so the caller need not do so.
509
+ #
510
+ # Argument +options+ is a hash of options for the new process;
511
+ # see {Execution Options}[rdoc-ref:Process@Execution+Options].
512
+ #
513
+ # The hash +options+ is passed to method Open3.popen3;
514
+ # two options have local effect in method Open3.capture3:
515
+ #
516
+ # - If entry <tt>options[:stdin_data]</tt> exists, the entry is removed
517
+ # and its string value is sent to the command's standard input:
518
+ #
519
+ # Open3.capture3('tee', stdin_data: 'Foo')
520
+ # # => ["Foo", "", #<Process::Status: pid 2319575 exit 0>]
521
+ #
522
+ # - If entry <tt>options[:binmode]</tt> exists, the entry is removed
523
+ # the internal streams are set to binary mode.
524
+ #
525
+ # The single required argument is one of the following:
526
+ #
527
+ # - +command_line+ if it is a string,
528
+ # and if it begins with a shell reserved word or special built-in,
529
+ # or if it contains one or more metacharacters.
530
+ # - +exe_path+ otherwise.
531
+ #
532
+ # <b>Argument +command_line+</b>
533
+ #
534
+ # \String argument +command_line+ is a command line to be passed to a shell;
535
+ # it must begin with a shell reserved word, begin with a special built-in,
536
+ # or contain meta characters:
537
+ #
538
+ # Open3.capture3('if true; then echo "Foo"; fi') # Shell reserved word.
539
+ # # => ["Foo\n", "", #<Process::Status: pid 2282025 exit 0>]
540
+ # Open3.capture3('echo') # Built-in.
541
+ # # => ["\n", "", #<Process::Status: pid 2282092 exit 0>]
542
+ # Open3.capture3('date > date.tmp') # Contains meta character.
543
+ # # => ["", "", #<Process::Status: pid 2282110 exit 0>]
544
+ #
545
+ # The command line may also contain arguments and options for the command:
546
+ #
547
+ # Open3.capture3('echo "Foo"')
548
+ # # => ["Foo\n", "", #<Process::Status: pid 2282092 exit 0>]
549
+ #
550
+ # <b>Argument +exe_path+</b>
551
+ #
552
+ # Argument +exe_path+ is one of the following:
553
+ #
554
+ # - The string path to an executable to be called.
555
+ # - A 2-element array containing the path to an executable
556
+ # and the string to be used as the name of the executing process.
557
+ #
558
+ # Example:
559
+ #
560
+ # Open3.capture3('/usr/bin/date')
561
+ # # => ["Thu Sep 28 05:03:51 PM CDT 2023\n", "", #<Process::Status: pid 2282300 exit 0>]
562
+ #
563
+ # Ruby invokes the executable directly, with no shell and no shell expansion:
564
+ #
565
+ # Open3.capture3('doesnt_exist') # Raises Errno::ENOENT
566
+ #
567
+ # If one or more +args+ is given, each is an argument or option
568
+ # to be passed to the executable:
569
+ #
570
+ # Open3.capture3('echo', 'C #')
571
+ # # => ["C #\n", "", #<Process::Status: pid 2282368 exit 0>]
572
+ # Open3.capture3('echo', 'hello', 'world')
573
+ # # => ["hello world\n", "", #<Process::Status: pid 2282372 exit 0>]
279
574
  #
280
575
  def capture3(*cmd)
281
576
  if Hash === cmd.last
@@ -309,34 +604,94 @@ module Open3
309
604
  end
310
605
  module_function :capture3
311
606
 
312
- # Open3.capture2 captures the standard output of a command.
607
+ # :call-seq:
608
+ # Open3.capture2([env, ] command_line, options = {}) -> [stdout_s, status]
609
+ # Open3.capture2([env, ] exe_path, *args, options = {}) -> [stdout_s, status]
610
+ #
611
+ # Basically a wrapper for Open3.popen3 that:
612
+ #
613
+ # - Creates a child process, by calling Open3.popen3 with the given arguments
614
+ # (except for certain entries in hash +options+; see below).
615
+ # - Returns as string +stdout_s+ the standard output of the child process.
616
+ # - Returns as +status+ a <tt>Process::Status</tt> object
617
+ # that represents the exit status of the child process.
618
+ #
619
+ # Returns the array <tt>[stdout_s, status]</tt>:
620
+ #
621
+ # stdout_s, status = Open3.capture2('echo "Foo"')
622
+ # # => ["Foo\n", #<Process::Status: pid 2326047 exit 0>]
623
+ #
624
+ # Like Process.spawn, this method has potential security vulnerabilities
625
+ # if called with untrusted input;
626
+ # see {Command Injection}[rdoc-ref:command_injection.rdoc].
627
+ #
628
+ # Unlike Process.spawn, this method waits for the child process to exit
629
+ # before returning, so the caller need not do so.
313
630
  #
314
- # stdout_str, status = Open3.capture2([env,] cmd... [, opts])
631
+ # Argument +options+ is a hash of options for the new process;
632
+ # see {Execution Options}[rdoc-ref:Process@Execution+Options].
315
633
  #
316
- # The arguments env, cmd and opts are passed to Open3.popen3 except
317
- # <code>opts[:stdin_data]</code> and <code>opts[:binmode]</code>. See Process.spawn.
634
+ # The hash +options+ is passed to method Open3.popen3;
635
+ # two options have local effect in method Open3.capture2:
318
636
  #
319
- # If <code>opts[:stdin_data]</code> is specified, it is sent to the command's standard input.
637
+ # - If entry <tt>options[:stdin_data]</tt> exists, the entry is removed
638
+ # and its string value is sent to the command's standard input:
320
639
  #
321
- # If <code>opts[:binmode]</code> is true, internal pipes are set to binary mode.
640
+ # Open3.capture2('tee', stdin_data: 'Foo')
641
+ #
642
+ # # => ["Foo", #<Process::Status: pid 2326087 exit 0>]
643
+ #
644
+ # the internal streams are set to binary mode.
645
+ #
646
+ # The single required argument is one of the following:
647
+ #
648
+ # - +command_line+ if it is a string,
649
+ # and if it begins with a shell reserved word or special built-in,
650
+ # or if it contains one or more metacharacters.
651
+ # - +exe_path+ otherwise.
652
+ #
653
+ # <b>Argument +command_line+</b>
654
+ #
655
+ # \String argument +command_line+ is a command line to be passed to a shell;
656
+ # it must begin with a shell reserved word, begin with a special built-in,
657
+ # or contain meta characters:
658
+ #
659
+ # Open3.capture2('if true; then echo "Foo"; fi') # Shell reserved word.
660
+ # # => ["Foo\n", #<Process::Status: pid 2326131 exit 0>]
661
+ # Open3.capture2('echo') # Built-in.
662
+ # # => ["\n", #<Process::Status: pid 2326139 exit 0>]
663
+ # Open3.capture2('date > date.tmp') # Contains meta character.
664
+ # # => ["", #<Process::Status: pid 2326174 exit 0>]
665
+ #
666
+ # The command line may also contain arguments and options for the command:
667
+ #
668
+ # Open3.capture2('echo "Foo"')
669
+ # # => ["Foo\n", #<Process::Status: pid 2326183 exit 0>]
670
+ #
671
+ # <b>Argument +exe_path+</b>
672
+ #
673
+ # Argument +exe_path+ is one of the following:
674
+ #
675
+ # - The string path to an executable to be called.
676
+ # - A 2-element array containing the path to an executable
677
+ # and the string to be used as the name of the executing process.
322
678
  #
323
679
  # Example:
324
680
  #
325
- # # factor is a command for integer factorization.
326
- # o, s = Open3.capture2("factor", :stdin_data=>"42")
327
- # p o #=> "42: 2 3 7\n"
328
- #
329
- # # generate x**2 graph in png using gnuplot.
330
- # gnuplot_commands = <<"End"
331
- # set terminal png
332
- # plot x**2, "-" with lines
333
- # 1 14
334
- # 2 1
335
- # 3 8
336
- # 4 5
337
- # e
338
- # End
339
- # image, s = Open3.capture2("gnuplot", :stdin_data=>gnuplot_commands, :binmode=>true)
681
+ # Open3.capture2('/usr/bin/date')
682
+ # # => ["Fri Sep 29 01:00:39 PM CDT 2023\n", #<Process::Status: pid 2326222 exit 0>]
683
+ #
684
+ # Ruby invokes the executable directly, with no shell and no shell expansion:
685
+ #
686
+ # Open3.capture2('doesnt_exist') # Raises Errno::ENOENT
687
+ #
688
+ # If one or more +args+ is given, each is an argument or option
689
+ # to be passed to the executable:
690
+ #
691
+ # Open3.capture2('echo', 'C #')
692
+ # # => ["C #\n", #<Process::Status: pid 2326267 exit 0>]
693
+ # Open3.capture2('echo', 'hello', 'world')
694
+ # # => ["hello world\n", #<Process::Status: pid 2326299 exit 0>]
340
695
  #
341
696
  def capture2(*cmd)
342
697
  if Hash === cmd.last
@@ -370,21 +725,94 @@ module Open3
370
725
  end
371
726
  module_function :capture2
372
727
 
373
- # Open3.capture2e captures the standard output and the standard error of a command.
728
+ # :call-seq:
729
+ # Open3.capture2e([env, ] command_line, options = {}) -> [stdout_and_stderr_s, status]
730
+ # Open3.capture2e([env, ] exe_path, *args, options = {}) -> [stdout_and_stderr_s, status]
731
+ #
732
+ # Basically a wrapper for Open3.popen3 that:
733
+ #
734
+ # - Creates a child process, by calling Open3.popen3 with the given arguments
735
+ # (except for certain entries in hash +options+; see below).
736
+ # - Returns as string +stdout_and_stderr_s+ the merged standard output
737
+ # and standard error of the child process.
738
+ # - Returns as +status+ a <tt>Process::Status</tt> object
739
+ # that represents the exit status of the child process.
740
+ #
741
+ # Returns the array <tt>[stdout_and_stderr_s, status]</tt>:
374
742
  #
375
- # stdout_and_stderr_str, status = Open3.capture2e([env,] cmd... [, opts])
743
+ # stdout_and_stderr_s, status = Open3.capture2e('echo "Foo"')
744
+ # # => ["Foo\n", #<Process::Status: pid 2371692 exit 0>]
376
745
  #
377
- # The arguments env, cmd and opts are passed to Open3.popen3 except
378
- # <code>opts[:stdin_data]</code> and <code>opts[:binmode]</code>. See Process.spawn.
746
+ # Like Process.spawn, this method has potential security vulnerabilities
747
+ # if called with untrusted input;
748
+ # see {Command Injection}[rdoc-ref:command_injection.rdoc].
379
749
  #
380
- # If <code>opts[:stdin_data]</code> is specified, it is sent to the command's standard input.
750
+ # Unlike Process.spawn, this method waits for the child process to exit
751
+ # before returning, so the caller need not do so.
381
752
  #
382
- # If <code>opts[:binmode]</code> is true, internal pipes are set to binary mode.
753
+ # Argument +options+ is a hash of options for the new process;
754
+ # see {Execution Options}[rdoc-ref:Process@Execution+Options].
755
+ #
756
+ # The hash +options+ is passed to method Open3.popen3;
757
+ # two options have local effect in method Open3.capture2e:
758
+ #
759
+ # - If entry <tt>options[:stdin_data]</tt> exists, the entry is removed
760
+ # and its string value is sent to the command's standard input:
761
+ #
762
+ # Open3.capture2e('tee', stdin_data: 'Foo')
763
+ # # => ["Foo", #<Process::Status: pid 2371732 exit 0>]
764
+ #
765
+ # the internal streams are set to binary mode.
766
+ #
767
+ # The single required argument is one of the following:
768
+ #
769
+ # - +command_line+ if it is a string,
770
+ # and if it begins with a shell reserved word or special built-in,
771
+ # or if it contains one or more metacharacters.
772
+ # - +exe_path+ otherwise.
773
+ #
774
+ # <b>Argument +command_line+</b>
775
+ #
776
+ # \String argument +command_line+ is a command line to be passed to a shell;
777
+ # it must begin with a shell reserved word, begin with a special built-in,
778
+ # or contain meta characters:
779
+ #
780
+ # Open3.capture2e('if true; then echo "Foo"; fi') # Shell reserved word.
781
+ # # => ["Foo\n", #<Process::Status: pid 2371740 exit 0>]
782
+ # Open3.capture2e('echo') # Built-in.
783
+ # # => ["\n", #<Process::Status: pid 2371774 exit 0>]
784
+ # Open3.capture2e('date > date.tmp') # Contains meta character.
785
+ # # => ["", #<Process::Status: pid 2371812 exit 0>]
786
+ #
787
+ # The command line may also contain arguments and options for the command:
788
+ #
789
+ # Open3.capture2e('echo "Foo"')
790
+ # # => ["Foo\n", #<Process::Status: pid 2326183 exit 0>]
791
+ #
792
+ # <b>Argument +exe_path+</b>
793
+ #
794
+ # Argument +exe_path+ is one of the following:
795
+ #
796
+ # - The string path to an executable to be called.
797
+ # - A 2-element array containing the path to an executable
798
+ # and the string to be used as the name of the executing process.
383
799
  #
384
800
  # Example:
385
801
  #
386
- # # capture make log
387
- # make_log, s = Open3.capture2e("make")
802
+ # Open3.capture2e('/usr/bin/date')
803
+ # # => ["Sat Sep 30 09:01:46 AM CDT 2023\n", #<Process::Status: pid 2371820 exit 0>]
804
+ #
805
+ # Ruby invokes the executable directly, with no shell and no shell expansion:
806
+ #
807
+ # Open3.capture2e('doesnt_exist') # Raises Errno::ENOENT
808
+ #
809
+ # If one or more +args+ is given, each is an argument or option
810
+ # to be passed to the executable:
811
+ #
812
+ # Open3.capture2e('echo', 'C #')
813
+ # # => ["C #\n", #<Process::Status: pid 2371856 exit 0>]
814
+ # Open3.capture2e('echo', 'hello', 'world')
815
+ # # => ["hello world\n", #<Process::Status: pid 2371894 exit 0>]
388
816
  #
389
817
  def capture2e(*cmd)
390
818
  if Hash === cmd.last
data/open3.gemspec CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
25
25
  # Specify which files should be added to the gem when it is released.
26
26
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
27
27
  spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
28
- `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
28
+ `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
29
29
  end
30
30
  spec.bindir = "exe"
31
31
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: open3
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yukihiro Matsumoto
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-12-14 00:00:00.000000000 Z
11
+ date: 2023-11-07 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Popen, but with stderr, too
14
14
  email:
@@ -53,7 +53,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  requirements: []
56
- rubygems_version: 3.4.0.dev
56
+ rubygems_version: 3.5.0.dev
57
57
  signing_key:
58
58
  specification_version: 4
59
59
  summary: Popen, but with stderr, too