open3 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +6 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +47 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/open3.rb +752 -0
- data/lib/open3/version.rb +3 -0
- data/open3.gemspec +27 -0
- metadata +56 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6f1dc26c8d711ed9394d746ea2fad6222de3cce813178dfb2d1aa52eb4fc0432
|
4
|
+
data.tar.gz: 121bb5a4ee245b344004afdf1bdabf0af421c5acaa306b08de5ff62865a896c3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ccb106824f5e6a47f6cf4324bc7638af0ea27fda57b87da181fb32c60cec309590a6d828635bb27afbfb13b3113e857a1d23cf88665a1630eeb5ea4e4e2dfe2c
|
7
|
+
data.tar.gz: '0918ee950ed53eeed0b64b569034496953b5e788d937bac2fb30c1ee1608c645adc5cd1c76c1982e263a3ae999af23e0783648c48a08e7f3a14d9fe1f3331924'
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved.
|
2
|
+
|
3
|
+
Redistribution and use in source and binary forms, with or without
|
4
|
+
modification, are permitted provided that the following conditions
|
5
|
+
are met:
|
6
|
+
1. Redistributions of source code must retain the above copyright
|
7
|
+
notice, this list of conditions and the following disclaimer.
|
8
|
+
2. Redistributions in binary form must reproduce the above copyright
|
9
|
+
notice, this list of conditions and the following disclaimer in the
|
10
|
+
documentation and/or other materials provided with the distribution.
|
11
|
+
|
12
|
+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
13
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
14
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
15
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
16
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
17
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
18
|
+
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
19
|
+
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
20
|
+
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
21
|
+
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
22
|
+
SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Open3
|
2
|
+
|
3
|
+
Open3 gives you access to stdin, stdout, and stderr when running other
|
4
|
+
programs.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'open3'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install open3
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
Open3 grants you access to stdin, stdout, stderr and a thread to wait for the child process when running another program.
|
25
|
+
You can specify various attributes, redirections, current directory, etc., of the program in the same way as for Process.spawn.
|
26
|
+
|
27
|
+
- Open3.popen3 : pipes for stdin, stdout, stderr
|
28
|
+
- Open3.popen2 : pipes for stdin, stdout
|
29
|
+
- Open3.popen2e : pipes for stdin, merged stdout and stderr
|
30
|
+
- Open3.capture3 : give a string for stdin; get strings for stdout, stderr
|
31
|
+
- Open3.capture2 : give a string for stdin; get a string for stdout
|
32
|
+
- Open3.capture2e : give a string for stdin; get a string for merged stdout and stderr
|
33
|
+
- Open3.pipeline_rw : pipes for first stdin and last stdout of a pipeline
|
34
|
+
- Open3.pipeline_r : pipe for last stdout of a pipeline
|
35
|
+
- Open3.pipeline_w : pipe for first stdin of a pipeline
|
36
|
+
- Open3.pipeline_start : run a pipeline without waiting
|
37
|
+
- Open3.pipeline : run a pipeline and wait for its completion
|
38
|
+
|
39
|
+
## Development
|
40
|
+
|
41
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
42
|
+
|
43
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
44
|
+
|
45
|
+
## Contributing
|
46
|
+
|
47
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/open3.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "open3"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/lib/open3.rb
ADDED
@@ -0,0 +1,752 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# = open3.rb: Popen, but with stderr, too
|
5
|
+
#
|
6
|
+
# Author:: Yukihiro Matsumoto
|
7
|
+
# Documentation:: Konrad Meyer
|
8
|
+
#
|
9
|
+
# Open3 gives you access to stdin, stdout, and stderr when running other
|
10
|
+
# programs.
|
11
|
+
#
|
12
|
+
|
13
|
+
#
|
14
|
+
# Open3 grants you access to stdin, stdout, stderr and a thread to wait for the
|
15
|
+
# child process when running another program.
|
16
|
+
# You can specify various attributes, redirections, current directory, etc., of
|
17
|
+
# the program in the same way as for Process.spawn.
|
18
|
+
#
|
19
|
+
# - Open3.popen3 : pipes for stdin, stdout, stderr
|
20
|
+
# - Open3.popen2 : pipes for stdin, stdout
|
21
|
+
# - Open3.popen2e : pipes for stdin, merged stdout and stderr
|
22
|
+
# - Open3.capture3 : give a string for stdin; get strings for stdout, stderr
|
23
|
+
# - Open3.capture2 : give a string for stdin; get a string for stdout
|
24
|
+
# - Open3.capture2e : give a string for stdin; get a string for merged stdout and stderr
|
25
|
+
# - Open3.pipeline_rw : pipes for first stdin and last stdout of a pipeline
|
26
|
+
# - Open3.pipeline_r : pipe for last stdout of a pipeline
|
27
|
+
# - Open3.pipeline_w : pipe for first stdin of a pipeline
|
28
|
+
# - Open3.pipeline_start : run a pipeline without waiting
|
29
|
+
# - Open3.pipeline : run a pipeline and wait for its completion
|
30
|
+
#
|
31
|
+
|
32
|
+
module Open3
|
33
|
+
|
34
|
+
# Open stdin, stdout, and stderr streams and start external executable.
|
35
|
+
# In addition, a thread to wait for the started process is created.
|
36
|
+
# The thread has a pid method and a thread variable :pid which is the pid of
|
37
|
+
# the started process.
|
38
|
+
#
|
39
|
+
# Block form:
|
40
|
+
#
|
41
|
+
# Open3.popen3([env,] cmd... [, opts]) {|stdin, stdout, stderr, wait_thr|
|
42
|
+
# pid = wait_thr.pid # pid of the started process.
|
43
|
+
# ...
|
44
|
+
# exit_status = wait_thr.value # Process::Status object returned.
|
45
|
+
# }
|
46
|
+
#
|
47
|
+
# Non-block form:
|
48
|
+
#
|
49
|
+
# stdin, stdout, stderr, wait_thr = Open3.popen3([env,] cmd... [, opts])
|
50
|
+
# pid = wait_thr[:pid] # pid of the started process
|
51
|
+
# ...
|
52
|
+
# stdin.close # stdin, stdout and stderr should be closed explicitly in this form.
|
53
|
+
# stdout.close
|
54
|
+
# stderr.close
|
55
|
+
# exit_status = wait_thr.value # Process::Status object returned.
|
56
|
+
#
|
57
|
+
# The parameters env, cmd, and opts are passed to Process.spawn.
|
58
|
+
# A commandline string and a list of argument strings can be accepted as follows:
|
59
|
+
#
|
60
|
+
# Open3.popen3("echo abc") {|i, o, e, t| ... }
|
61
|
+
# Open3.popen3("echo", "abc") {|i, o, e, t| ... }
|
62
|
+
# Open3.popen3(["echo", "argv0"], "abc") {|i, o, e, t| ... }
|
63
|
+
#
|
64
|
+
# If the last parameter, opts, is a Hash, it is recognized as an option for Process.spawn.
|
65
|
+
#
|
66
|
+
# Open3.popen3("pwd", :chdir=>"/") {|i,o,e,t|
|
67
|
+
# p o.read.chomp #=> "/"
|
68
|
+
# }
|
69
|
+
#
|
70
|
+
# wait_thr.value waits for the termination of the process.
|
71
|
+
# The block form also waits for the process when it returns.
|
72
|
+
#
|
73
|
+
# Closing stdin, stdout and stderr does not wait for the process to complete.
|
74
|
+
#
|
75
|
+
# You should be careful to avoid deadlocks.
|
76
|
+
# Since pipes are fixed length buffers,
|
77
|
+
# Open3.popen3("prog") {|i, o, e, t| o.read } deadlocks if
|
78
|
+
# the program generates too much output on stderr.
|
79
|
+
# You should read stdout and stderr simultaneously (using threads or IO.select).
|
80
|
+
# However, if you don't need stderr output, you can use Open3.popen2.
|
81
|
+
# If merged stdout and stderr output is not a problem, you can use Open3.popen2e.
|
82
|
+
# If you really need stdout and stderr output as separate strings, you can consider Open3.capture3.
|
83
|
+
#
|
84
|
+
def popen3(*cmd, &block)
|
85
|
+
if Hash === cmd.last
|
86
|
+
opts = cmd.pop.dup
|
87
|
+
else
|
88
|
+
opts = {}
|
89
|
+
end
|
90
|
+
|
91
|
+
in_r, in_w = IO.pipe
|
92
|
+
opts[:in] = in_r
|
93
|
+
in_w.sync = true
|
94
|
+
|
95
|
+
out_r, out_w = IO.pipe
|
96
|
+
opts[:out] = out_w
|
97
|
+
|
98
|
+
err_r, err_w = IO.pipe
|
99
|
+
opts[:err] = err_w
|
100
|
+
|
101
|
+
popen_run(cmd, opts, [in_r, out_w, err_w], [in_w, out_r, err_r], &block)
|
102
|
+
end
|
103
|
+
module_function :popen3
|
104
|
+
|
105
|
+
# Open3.popen2 is similar to Open3.popen3 except that it doesn't create a pipe for
|
106
|
+
# the standard error stream.
|
107
|
+
#
|
108
|
+
# Block form:
|
109
|
+
#
|
110
|
+
# Open3.popen2([env,] cmd... [, opts]) {|stdin, stdout, wait_thr|
|
111
|
+
# pid = wait_thr.pid # pid of the started process.
|
112
|
+
# ...
|
113
|
+
# exit_status = wait_thr.value # Process::Status object returned.
|
114
|
+
# }
|
115
|
+
#
|
116
|
+
# Non-block form:
|
117
|
+
#
|
118
|
+
# stdin, stdout, wait_thr = Open3.popen2([env,] cmd... [, opts])
|
119
|
+
# ...
|
120
|
+
# stdin.close # stdin and stdout should be closed explicitly in this form.
|
121
|
+
# stdout.close
|
122
|
+
#
|
123
|
+
# See Process.spawn for the optional hash arguments _env_ and _opts_.
|
124
|
+
#
|
125
|
+
# Example:
|
126
|
+
#
|
127
|
+
# Open3.popen2("wc -c") {|i,o,t|
|
128
|
+
# i.print "answer to life the universe and everything"
|
129
|
+
# i.close
|
130
|
+
# p o.gets #=> "42\n"
|
131
|
+
# }
|
132
|
+
#
|
133
|
+
# Open3.popen2("bc -q") {|i,o,t|
|
134
|
+
# i.puts "obase=13"
|
135
|
+
# i.puts "6 * 9"
|
136
|
+
# p o.gets #=> "42\n"
|
137
|
+
# }
|
138
|
+
#
|
139
|
+
# Open3.popen2("dc") {|i,o,t|
|
140
|
+
# i.print "42P"
|
141
|
+
# i.close
|
142
|
+
# p o.read #=> "*"
|
143
|
+
# }
|
144
|
+
#
|
145
|
+
def popen2(*cmd, &block)
|
146
|
+
if Hash === cmd.last
|
147
|
+
opts = cmd.pop.dup
|
148
|
+
else
|
149
|
+
opts = {}
|
150
|
+
end
|
151
|
+
|
152
|
+
in_r, in_w = IO.pipe
|
153
|
+
opts[:in] = in_r
|
154
|
+
in_w.sync = true
|
155
|
+
|
156
|
+
out_r, out_w = IO.pipe
|
157
|
+
opts[:out] = out_w
|
158
|
+
|
159
|
+
popen_run(cmd, opts, [in_r, out_w], [in_w, out_r], &block)
|
160
|
+
end
|
161
|
+
module_function :popen2
|
162
|
+
|
163
|
+
# Open3.popen2e is similar to Open3.popen3 except that it merges
|
164
|
+
# the standard output stream and the standard error stream.
|
165
|
+
#
|
166
|
+
# Block form:
|
167
|
+
#
|
168
|
+
# Open3.popen2e([env,] cmd... [, opts]) {|stdin, stdout_and_stderr, wait_thr|
|
169
|
+
# pid = wait_thr.pid # pid of the started process.
|
170
|
+
# ...
|
171
|
+
# exit_status = wait_thr.value # Process::Status object returned.
|
172
|
+
# }
|
173
|
+
#
|
174
|
+
# Non-block form:
|
175
|
+
#
|
176
|
+
# stdin, stdout_and_stderr, wait_thr = Open3.popen2e([env,] cmd... [, opts])
|
177
|
+
# ...
|
178
|
+
# stdin.close # stdin and stdout_and_stderr should be closed explicitly in this form.
|
179
|
+
# stdout_and_stderr.close
|
180
|
+
#
|
181
|
+
# See Process.spawn for the optional hash arguments _env_ and _opts_.
|
182
|
+
#
|
183
|
+
# Example:
|
184
|
+
# # check gcc warnings
|
185
|
+
# source = "foo.c"
|
186
|
+
# Open3.popen2e("gcc", "-Wall", source) {|i,oe,t|
|
187
|
+
# oe.each {|line|
|
188
|
+
# if /warning/ =~ line
|
189
|
+
# ...
|
190
|
+
# end
|
191
|
+
# }
|
192
|
+
# }
|
193
|
+
#
|
194
|
+
def popen2e(*cmd, &block)
|
195
|
+
if Hash === cmd.last
|
196
|
+
opts = cmd.pop.dup
|
197
|
+
else
|
198
|
+
opts = {}
|
199
|
+
end
|
200
|
+
|
201
|
+
in_r, in_w = IO.pipe
|
202
|
+
opts[:in] = in_r
|
203
|
+
in_w.sync = true
|
204
|
+
|
205
|
+
out_r, out_w = IO.pipe
|
206
|
+
opts[[:out, :err]] = out_w
|
207
|
+
|
208
|
+
popen_run(cmd, opts, [in_r, out_w], [in_w, out_r], &block)
|
209
|
+
end
|
210
|
+
module_function :popen2e
|
211
|
+
|
212
|
+
def popen_run(cmd, opts, child_io, parent_io) # :nodoc:
|
213
|
+
pid = spawn(*cmd, opts)
|
214
|
+
wait_thr = Process.detach(pid)
|
215
|
+
child_io.each(&:close)
|
216
|
+
result = [*parent_io, wait_thr]
|
217
|
+
if defined? yield
|
218
|
+
begin
|
219
|
+
return yield(*result)
|
220
|
+
ensure
|
221
|
+
parent_io.each(&:close)
|
222
|
+
wait_thr.join
|
223
|
+
end
|
224
|
+
end
|
225
|
+
result
|
226
|
+
end
|
227
|
+
module_function :popen_run
|
228
|
+
class << self
|
229
|
+
private :popen_run
|
230
|
+
end
|
231
|
+
|
232
|
+
# Open3.capture3 captures the standard output and the standard error of a command.
|
233
|
+
#
|
234
|
+
# stdout_str, stderr_str, status = Open3.capture3([env,] cmd... [, opts])
|
235
|
+
#
|
236
|
+
# The arguments env, cmd and opts are passed to Open3.popen3 except
|
237
|
+
# <code>opts[:stdin_data]</code> and <code>opts[:binmode]</code>. See Process.spawn.
|
238
|
+
#
|
239
|
+
# If <code>opts[:stdin_data]</code> is specified, it is sent to the command's standard input.
|
240
|
+
#
|
241
|
+
# If <code>opts[:binmode]</code> is true, internal pipes are set to binary mode.
|
242
|
+
#
|
243
|
+
# Examples:
|
244
|
+
#
|
245
|
+
# # dot is a command of graphviz.
|
246
|
+
# graph = <<'End'
|
247
|
+
# digraph g {
|
248
|
+
# a -> b
|
249
|
+
# }
|
250
|
+
# End
|
251
|
+
# drawn_graph, dot_log = Open3.capture3("dot -v", :stdin_data=>graph)
|
252
|
+
#
|
253
|
+
# o, e, s = Open3.capture3("echo abc; sort >&2", :stdin_data=>"foo\nbar\nbaz\n")
|
254
|
+
# p o #=> "abc\n"
|
255
|
+
# p e #=> "bar\nbaz\nfoo\n"
|
256
|
+
# p s #=> #<Process::Status: pid 32682 exit 0>
|
257
|
+
#
|
258
|
+
# # generate a thumbnail image using the convert command of ImageMagick.
|
259
|
+
# # However, if the image is really stored in a file,
|
260
|
+
# # system("convert", "-thumbnail", "80", "png:#{filename}", "png:-") is better
|
261
|
+
# # because of reduced memory consumption.
|
262
|
+
# # But if the image is stored in a DB or generated by the gnuplot Open3.capture2 example,
|
263
|
+
# # Open3.capture3 should be considered.
|
264
|
+
# #
|
265
|
+
# image = File.read("/usr/share/openclipart/png/animals/mammals/sheep-md-v0.1.png", :binmode=>true)
|
266
|
+
# thumbnail, err, s = Open3.capture3("convert -thumbnail 80 png:- png:-", :stdin_data=>image, :binmode=>true)
|
267
|
+
# if s.success?
|
268
|
+
# STDOUT.binmode; print thumbnail
|
269
|
+
# end
|
270
|
+
#
|
271
|
+
def capture3(*cmd)
|
272
|
+
if Hash === cmd.last
|
273
|
+
opts = cmd.pop.dup
|
274
|
+
else
|
275
|
+
opts = {}
|
276
|
+
end
|
277
|
+
|
278
|
+
stdin_data = opts.delete(:stdin_data) || ''
|
279
|
+
binmode = opts.delete(:binmode)
|
280
|
+
|
281
|
+
popen3(*cmd, opts) {|i, o, e, t|
|
282
|
+
if binmode
|
283
|
+
i.binmode
|
284
|
+
o.binmode
|
285
|
+
e.binmode
|
286
|
+
end
|
287
|
+
out_reader = Thread.new { o.read }
|
288
|
+
err_reader = Thread.new { e.read }
|
289
|
+
begin
|
290
|
+
if stdin_data.respond_to? :readpartial
|
291
|
+
IO.copy_stream(stdin_data, i)
|
292
|
+
else
|
293
|
+
i.write stdin_data
|
294
|
+
end
|
295
|
+
rescue Errno::EPIPE
|
296
|
+
end
|
297
|
+
i.close
|
298
|
+
[out_reader.value, err_reader.value, t.value]
|
299
|
+
}
|
300
|
+
end
|
301
|
+
module_function :capture3
|
302
|
+
|
303
|
+
# Open3.capture2 captures the standard output of a command.
|
304
|
+
#
|
305
|
+
# stdout_str, status = Open3.capture2([env,] cmd... [, opts])
|
306
|
+
#
|
307
|
+
# The arguments env, cmd and opts are passed to Open3.popen3 except
|
308
|
+
# <code>opts[:stdin_data]</code> and <code>opts[:binmode]</code>. See Process.spawn.
|
309
|
+
#
|
310
|
+
# If <code>opts[:stdin_data]</code> is specified, it is sent to the command's standard input.
|
311
|
+
#
|
312
|
+
# If <code>opts[:binmode]</code> is true, internal pipes are set to binary mode.
|
313
|
+
#
|
314
|
+
# Example:
|
315
|
+
#
|
316
|
+
# # factor is a command for integer factorization.
|
317
|
+
# o, s = Open3.capture2("factor", :stdin_data=>"42")
|
318
|
+
# p o #=> "42: 2 3 7\n"
|
319
|
+
#
|
320
|
+
# # generate x**2 graph in png using gnuplot.
|
321
|
+
# gnuplot_commands = <<"End"
|
322
|
+
# set terminal png
|
323
|
+
# plot x**2, "-" with lines
|
324
|
+
# 1 14
|
325
|
+
# 2 1
|
326
|
+
# 3 8
|
327
|
+
# 4 5
|
328
|
+
# e
|
329
|
+
# End
|
330
|
+
# image, s = Open3.capture2("gnuplot", :stdin_data=>gnuplot_commands, :binmode=>true)
|
331
|
+
#
|
332
|
+
def capture2(*cmd)
|
333
|
+
if Hash === cmd.last
|
334
|
+
opts = cmd.pop.dup
|
335
|
+
else
|
336
|
+
opts = {}
|
337
|
+
end
|
338
|
+
|
339
|
+
stdin_data = opts.delete(:stdin_data)
|
340
|
+
binmode = opts.delete(:binmode)
|
341
|
+
|
342
|
+
popen2(*cmd, opts) {|i, o, t|
|
343
|
+
if binmode
|
344
|
+
i.binmode
|
345
|
+
o.binmode
|
346
|
+
end
|
347
|
+
out_reader = Thread.new { o.read }
|
348
|
+
if stdin_data
|
349
|
+
begin
|
350
|
+
if stdin_data.respond_to? :readpartial
|
351
|
+
IO.copy_stream(stdin_data, i)
|
352
|
+
else
|
353
|
+
i.write stdin_data
|
354
|
+
end
|
355
|
+
rescue Errno::EPIPE
|
356
|
+
end
|
357
|
+
end
|
358
|
+
i.close
|
359
|
+
[out_reader.value, t.value]
|
360
|
+
}
|
361
|
+
end
|
362
|
+
module_function :capture2
|
363
|
+
|
364
|
+
# Open3.capture2e captures the standard output and the standard error of a command.
|
365
|
+
#
|
366
|
+
# stdout_and_stderr_str, status = Open3.capture2e([env,] cmd... [, opts])
|
367
|
+
#
|
368
|
+
# The arguments env, cmd and opts are passed to Open3.popen3 except
|
369
|
+
# <code>opts[:stdin_data]</code> and <code>opts[:binmode]</code>. See Process.spawn.
|
370
|
+
#
|
371
|
+
# If <code>opts[:stdin_data]</code> is specified, it is sent to the command's standard input.
|
372
|
+
#
|
373
|
+
# If <code>opts[:binmode]</code> is true, internal pipes are set to binary mode.
|
374
|
+
#
|
375
|
+
# Example:
|
376
|
+
#
|
377
|
+
# # capture make log
|
378
|
+
# make_log, s = Open3.capture2e("make")
|
379
|
+
#
|
380
|
+
def capture2e(*cmd)
|
381
|
+
if Hash === cmd.last
|
382
|
+
opts = cmd.pop.dup
|
383
|
+
else
|
384
|
+
opts = {}
|
385
|
+
end
|
386
|
+
|
387
|
+
stdin_data = opts.delete(:stdin_data)
|
388
|
+
binmode = opts.delete(:binmode)
|
389
|
+
|
390
|
+
popen2e(*cmd, opts) {|i, oe, t|
|
391
|
+
if binmode
|
392
|
+
i.binmode
|
393
|
+
oe.binmode
|
394
|
+
end
|
395
|
+
outerr_reader = Thread.new { oe.read }
|
396
|
+
if stdin_data
|
397
|
+
begin
|
398
|
+
if stdin_data.respond_to? :readpartial
|
399
|
+
IO.copy_stream(stdin_data, i)
|
400
|
+
else
|
401
|
+
i.write stdin_data
|
402
|
+
end
|
403
|
+
rescue Errno::EPIPE
|
404
|
+
end
|
405
|
+
end
|
406
|
+
i.close
|
407
|
+
[outerr_reader.value, t.value]
|
408
|
+
}
|
409
|
+
end
|
410
|
+
module_function :capture2e
|
411
|
+
|
412
|
+
# Open3.pipeline_rw starts a list of commands as a pipeline with pipes
|
413
|
+
# which connect to stdin of the first command and stdout of the last command.
|
414
|
+
#
|
415
|
+
# Open3.pipeline_rw(cmd1, cmd2, ... [, opts]) {|first_stdin, last_stdout, wait_threads|
|
416
|
+
# ...
|
417
|
+
# }
|
418
|
+
#
|
419
|
+
# first_stdin, last_stdout, wait_threads = Open3.pipeline_rw(cmd1, cmd2, ... [, opts])
|
420
|
+
# ...
|
421
|
+
# first_stdin.close
|
422
|
+
# last_stdout.close
|
423
|
+
#
|
424
|
+
# Each cmd is a string or an array.
|
425
|
+
# If it is an array, the elements are passed to Process.spawn.
|
426
|
+
#
|
427
|
+
# cmd:
|
428
|
+
# commandline command line string which is passed to a shell
|
429
|
+
# [env, commandline, opts] command line string which is passed to a shell
|
430
|
+
# [env, cmdname, arg1, ..., opts] command name and one or more arguments (no shell)
|
431
|
+
# [env, [cmdname, argv0], arg1, ..., opts] command name and arguments including argv[0] (no shell)
|
432
|
+
#
|
433
|
+
# Note that env and opts are optional, as for Process.spawn.
|
434
|
+
#
|
435
|
+
# The options to pass to Process.spawn are constructed by merging
|
436
|
+
# +opts+, the last hash element of the array, and
|
437
|
+
# specifications for the pipes between each of the commands.
|
438
|
+
#
|
439
|
+
# Example:
|
440
|
+
#
|
441
|
+
# Open3.pipeline_rw("tr -dc A-Za-z", "wc -c") {|i, o, ts|
|
442
|
+
# i.puts "All persons more than a mile high to leave the court."
|
443
|
+
# i.close
|
444
|
+
# p o.gets #=> "42\n"
|
445
|
+
# }
|
446
|
+
#
|
447
|
+
# Open3.pipeline_rw("sort", "cat -n") {|stdin, stdout, wait_thrs|
|
448
|
+
# stdin.puts "foo"
|
449
|
+
# stdin.puts "bar"
|
450
|
+
# stdin.puts "baz"
|
451
|
+
# stdin.close # send EOF to sort.
|
452
|
+
# p stdout.read #=> " 1\tbar\n 2\tbaz\n 3\tfoo\n"
|
453
|
+
# }
|
454
|
+
def pipeline_rw(*cmds, &block)
|
455
|
+
if Hash === cmds.last
|
456
|
+
opts = cmds.pop.dup
|
457
|
+
else
|
458
|
+
opts = {}
|
459
|
+
end
|
460
|
+
|
461
|
+
in_r, in_w = IO.pipe
|
462
|
+
opts[:in] = in_r
|
463
|
+
in_w.sync = true
|
464
|
+
|
465
|
+
out_r, out_w = IO.pipe
|
466
|
+
opts[:out] = out_w
|
467
|
+
|
468
|
+
pipeline_run(cmds, opts, [in_r, out_w], [in_w, out_r], &block)
|
469
|
+
end
|
470
|
+
module_function :pipeline_rw
|
471
|
+
|
472
|
+
# Open3.pipeline_r starts a list of commands as a pipeline with a pipe
|
473
|
+
# which connects to stdout of the last command.
|
474
|
+
#
|
475
|
+
# Open3.pipeline_r(cmd1, cmd2, ... [, opts]) {|last_stdout, wait_threads|
|
476
|
+
# ...
|
477
|
+
# }
|
478
|
+
#
|
479
|
+
# last_stdout, wait_threads = Open3.pipeline_r(cmd1, cmd2, ... [, opts])
|
480
|
+
# ...
|
481
|
+
# last_stdout.close
|
482
|
+
#
|
483
|
+
# Each cmd is a string or an array.
|
484
|
+
# If it is an array, the elements are passed to Process.spawn.
|
485
|
+
#
|
486
|
+
# cmd:
|
487
|
+
# commandline command line string which is passed to a shell
|
488
|
+
# [env, commandline, opts] command line string which is passed to a shell
|
489
|
+
# [env, cmdname, arg1, ..., opts] command name and one or more arguments (no shell)
|
490
|
+
# [env, [cmdname, argv0], arg1, ..., opts] command name and arguments including argv[0] (no shell)
|
491
|
+
#
|
492
|
+
# Note that env and opts are optional, as for Process.spawn.
|
493
|
+
#
|
494
|
+
# Example:
|
495
|
+
#
|
496
|
+
# Open3.pipeline_r("zcat /var/log/apache2/access.log.*.gz",
|
497
|
+
# [{"LANG"=>"C"}, "grep", "GET /favicon.ico"],
|
498
|
+
# "logresolve") {|o, ts|
|
499
|
+
# o.each_line {|line|
|
500
|
+
# ...
|
501
|
+
# }
|
502
|
+
# }
|
503
|
+
#
|
504
|
+
# Open3.pipeline_r("yes", "head -10") {|o, ts|
|
505
|
+
# p o.read #=> "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"
|
506
|
+
# p ts[0].value #=> #<Process::Status: pid 24910 SIGPIPE (signal 13)>
|
507
|
+
# p ts[1].value #=> #<Process::Status: pid 24913 exit 0>
|
508
|
+
# }
|
509
|
+
#
|
510
|
+
def pipeline_r(*cmds, &block)
|
511
|
+
if Hash === cmds.last
|
512
|
+
opts = cmds.pop.dup
|
513
|
+
else
|
514
|
+
opts = {}
|
515
|
+
end
|
516
|
+
|
517
|
+
out_r, out_w = IO.pipe
|
518
|
+
opts[:out] = out_w
|
519
|
+
|
520
|
+
pipeline_run(cmds, opts, [out_w], [out_r], &block)
|
521
|
+
end
|
522
|
+
module_function :pipeline_r
|
523
|
+
|
524
|
+
# Open3.pipeline_w starts a list of commands as a pipeline with a pipe
|
525
|
+
# which connects to stdin of the first command.
|
526
|
+
#
|
527
|
+
# Open3.pipeline_w(cmd1, cmd2, ... [, opts]) {|first_stdin, wait_threads|
|
528
|
+
# ...
|
529
|
+
# }
|
530
|
+
#
|
531
|
+
# first_stdin, wait_threads = Open3.pipeline_w(cmd1, cmd2, ... [, opts])
|
532
|
+
# ...
|
533
|
+
# first_stdin.close
|
534
|
+
#
|
535
|
+
# Each cmd is a string or an array.
|
536
|
+
# If it is an array, the elements are passed to Process.spawn.
|
537
|
+
#
|
538
|
+
# cmd:
|
539
|
+
# commandline command line string which is passed to a shell
|
540
|
+
# [env, commandline, opts] command line string which is passed to a shell
|
541
|
+
# [env, cmdname, arg1, ..., opts] command name and one or more arguments (no shell)
|
542
|
+
# [env, [cmdname, argv0], arg1, ..., opts] command name and arguments including argv[0] (no shell)
|
543
|
+
#
|
544
|
+
# Note that env and opts are optional, as for Process.spawn.
|
545
|
+
#
|
546
|
+
# Example:
|
547
|
+
#
|
548
|
+
# Open3.pipeline_w("bzip2 -c", :out=>"/tmp/hello.bz2") {|i, ts|
|
549
|
+
# i.puts "hello"
|
550
|
+
# }
|
551
|
+
#
|
552
|
+
def pipeline_w(*cmds, &block)
|
553
|
+
if Hash === cmds.last
|
554
|
+
opts = cmds.pop.dup
|
555
|
+
else
|
556
|
+
opts = {}
|
557
|
+
end
|
558
|
+
|
559
|
+
in_r, in_w = IO.pipe
|
560
|
+
opts[:in] = in_r
|
561
|
+
in_w.sync = true
|
562
|
+
|
563
|
+
pipeline_run(cmds, opts, [in_r], [in_w], &block)
|
564
|
+
end
|
565
|
+
module_function :pipeline_w
|
566
|
+
|
567
|
+
# Open3.pipeline_start starts a list of commands as a pipeline.
|
568
|
+
# No pipes are created for stdin of the first command and
|
569
|
+
# stdout of the last command.
|
570
|
+
#
|
571
|
+
# Open3.pipeline_start(cmd1, cmd2, ... [, opts]) {|wait_threads|
|
572
|
+
# ...
|
573
|
+
# }
|
574
|
+
#
|
575
|
+
# wait_threads = Open3.pipeline_start(cmd1, cmd2, ... [, opts])
|
576
|
+
# ...
|
577
|
+
#
|
578
|
+
# Each cmd is a string or an array.
|
579
|
+
# If it is an array, the elements are passed to Process.spawn.
|
580
|
+
#
|
581
|
+
# cmd:
|
582
|
+
# commandline command line string which is passed to a shell
|
583
|
+
# [env, commandline, opts] command line string which is passed to a shell
|
584
|
+
# [env, cmdname, arg1, ..., opts] command name and one or more arguments (no shell)
|
585
|
+
# [env, [cmdname, argv0], arg1, ..., opts] command name and arguments including argv[0] (no shell)
|
586
|
+
#
|
587
|
+
# Note that env and opts are optional, as for Process.spawn.
|
588
|
+
#
|
589
|
+
# Example:
|
590
|
+
#
|
591
|
+
# # Run xeyes in 10 seconds.
|
592
|
+
# Open3.pipeline_start("xeyes") {|ts|
|
593
|
+
# sleep 10
|
594
|
+
# t = ts[0]
|
595
|
+
# Process.kill("TERM", t.pid)
|
596
|
+
# p t.value #=> #<Process::Status: pid 911 SIGTERM (signal 15)>
|
597
|
+
# }
|
598
|
+
#
|
599
|
+
# # Convert pdf to ps and send it to a printer.
|
600
|
+
# # Collect error message of pdftops and lpr.
|
601
|
+
# pdf_file = "paper.pdf"
|
602
|
+
# printer = "printer-name"
|
603
|
+
# err_r, err_w = IO.pipe
|
604
|
+
# Open3.pipeline_start(["pdftops", pdf_file, "-"],
|
605
|
+
# ["lpr", "-P#{printer}"],
|
606
|
+
# :err=>err_w) {|ts|
|
607
|
+
# err_w.close
|
608
|
+
# p err_r.read # error messages of pdftops and lpr.
|
609
|
+
# }
|
610
|
+
#
|
611
|
+
def pipeline_start(*cmds, &block)
|
612
|
+
if Hash === cmds.last
|
613
|
+
opts = cmds.pop.dup
|
614
|
+
else
|
615
|
+
opts = {}
|
616
|
+
end
|
617
|
+
|
618
|
+
if block
|
619
|
+
pipeline_run(cmds, opts, [], [], &block)
|
620
|
+
else
|
621
|
+
ts, = pipeline_run(cmds, opts, [], [])
|
622
|
+
ts
|
623
|
+
end
|
624
|
+
end
|
625
|
+
module_function :pipeline_start
|
626
|
+
|
627
|
+
# Open3.pipeline starts a list of commands as a pipeline.
|
628
|
+
# It waits for the completion of the commands.
|
629
|
+
# No pipes are created for stdin of the first command and
|
630
|
+
# stdout of the last command.
|
631
|
+
#
|
632
|
+
# status_list = Open3.pipeline(cmd1, cmd2, ... [, opts])
|
633
|
+
#
|
634
|
+
# Each cmd is a string or an array.
|
635
|
+
# If it is an array, the elements are passed to Process.spawn.
|
636
|
+
#
|
637
|
+
# cmd:
|
638
|
+
# commandline command line string which is passed to a shell
|
639
|
+
# [env, commandline, opts] command line string which is passed to a shell
|
640
|
+
# [env, cmdname, arg1, ..., opts] command name and one or more arguments (no shell)
|
641
|
+
# [env, [cmdname, argv0], arg1, ..., opts] command name and arguments including argv[0] (no shell)
|
642
|
+
#
|
643
|
+
# Note that env and opts are optional, as Process.spawn.
|
644
|
+
#
|
645
|
+
# Example:
|
646
|
+
#
|
647
|
+
# fname = "/usr/share/man/man1/ruby.1.gz"
|
648
|
+
# p Open3.pipeline(["zcat", fname], "nroff -man", "less")
|
649
|
+
# #=> [#<Process::Status: pid 11817 exit 0>,
|
650
|
+
# # #<Process::Status: pid 11820 exit 0>,
|
651
|
+
# # #<Process::Status: pid 11828 exit 0>]
|
652
|
+
#
|
653
|
+
# fname = "/usr/share/man/man1/ls.1.gz"
|
654
|
+
# Open3.pipeline(["zcat", fname], "nroff -man", "colcrt")
|
655
|
+
#
|
656
|
+
# # convert PDF to PS and send to a printer by lpr
|
657
|
+
# pdf_file = "paper.pdf"
|
658
|
+
# printer = "printer-name"
|
659
|
+
# Open3.pipeline(["pdftops", pdf_file, "-"],
|
660
|
+
# ["lpr", "-P#{printer}"])
|
661
|
+
#
|
662
|
+
# # count lines
|
663
|
+
# Open3.pipeline("sort", "uniq -c", :in=>"names.txt", :out=>"count")
|
664
|
+
#
|
665
|
+
# # cyclic pipeline
|
666
|
+
# r,w = IO.pipe
|
667
|
+
# w.print "ibase=14\n10\n"
|
668
|
+
# Open3.pipeline("bc", "tee /dev/tty", :in=>r, :out=>w)
|
669
|
+
# #=> 14
|
670
|
+
# # 18
|
671
|
+
# # 22
|
672
|
+
# # 30
|
673
|
+
# # 42
|
674
|
+
# # 58
|
675
|
+
# # 78
|
676
|
+
# # 106
|
677
|
+
# # 202
|
678
|
+
#
|
679
|
+
def pipeline(*cmds)
|
680
|
+
if Hash === cmds.last
|
681
|
+
opts = cmds.pop.dup
|
682
|
+
else
|
683
|
+
opts = {}
|
684
|
+
end
|
685
|
+
|
686
|
+
pipeline_run(cmds, opts, [], []) {|ts|
|
687
|
+
ts.map(&:value)
|
688
|
+
}
|
689
|
+
end
|
690
|
+
module_function :pipeline
|
691
|
+
|
692
|
+
def pipeline_run(cmds, pipeline_opts, child_io, parent_io) # :nodoc:
|
693
|
+
if cmds.empty?
|
694
|
+
raise ArgumentError, "no commands"
|
695
|
+
end
|
696
|
+
|
697
|
+
opts_base = pipeline_opts.dup
|
698
|
+
opts_base.delete :in
|
699
|
+
opts_base.delete :out
|
700
|
+
|
701
|
+
wait_thrs = []
|
702
|
+
r = nil
|
703
|
+
cmds.each_with_index {|cmd, i|
|
704
|
+
cmd_opts = opts_base.dup
|
705
|
+
if String === cmd
|
706
|
+
cmd = [cmd]
|
707
|
+
else
|
708
|
+
cmd_opts.update cmd.pop if Hash === cmd.last
|
709
|
+
end
|
710
|
+
if i == 0
|
711
|
+
if !cmd_opts.include?(:in)
|
712
|
+
if pipeline_opts.include?(:in)
|
713
|
+
cmd_opts[:in] = pipeline_opts[:in]
|
714
|
+
end
|
715
|
+
end
|
716
|
+
else
|
717
|
+
cmd_opts[:in] = r
|
718
|
+
end
|
719
|
+
if i != cmds.length - 1
|
720
|
+
r2, w2 = IO.pipe
|
721
|
+
cmd_opts[:out] = w2
|
722
|
+
else
|
723
|
+
if !cmd_opts.include?(:out)
|
724
|
+
if pipeline_opts.include?(:out)
|
725
|
+
cmd_opts[:out] = pipeline_opts[:out]
|
726
|
+
end
|
727
|
+
end
|
728
|
+
end
|
729
|
+
pid = spawn(*cmd, cmd_opts)
|
730
|
+
wait_thrs << Process.detach(pid)
|
731
|
+
r&.close
|
732
|
+
w2&.close
|
733
|
+
r = r2
|
734
|
+
}
|
735
|
+
result = parent_io + [wait_thrs]
|
736
|
+
child_io.each(&:close)
|
737
|
+
if defined? yield
|
738
|
+
begin
|
739
|
+
return yield(*result)
|
740
|
+
ensure
|
741
|
+
parent_io.each(&:close)
|
742
|
+
wait_thrs.each(&:join)
|
743
|
+
end
|
744
|
+
end
|
745
|
+
result
|
746
|
+
end
|
747
|
+
module_function :pipeline_run
|
748
|
+
class << self
|
749
|
+
private :pipeline_run
|
750
|
+
end
|
751
|
+
|
752
|
+
end
|
data/open3.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
lib = File.expand_path("lib", __dir__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "open3/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "open3"
|
7
|
+
spec.version = Open3::VERSION
|
8
|
+
spec.authors = ["Hiroshi SHIBATA"]
|
9
|
+
spec.email = ["hsbt@ruby-lang.org"]
|
10
|
+
|
11
|
+
spec.summary = %q{Popen, but with stderr, too}
|
12
|
+
spec.description = spec.summary
|
13
|
+
spec.homepage = "https://github.com/ruby/open3"
|
14
|
+
spec.license = "BSD-2-Clause"
|
15
|
+
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
17
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
18
|
+
|
19
|
+
# Specify which files should be added to the gem when it is released.
|
20
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
21
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
22
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
23
|
+
end
|
24
|
+
spec.bindir = "exe"
|
25
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
26
|
+
spec.require_paths = ["lib"]
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: open3
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Hiroshi SHIBATA
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-11-06 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Popen, but with stderr, too
|
14
|
+
email:
|
15
|
+
- hsbt@ruby-lang.org
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- ".gitignore"
|
21
|
+
- ".travis.yml"
|
22
|
+
- Gemfile
|
23
|
+
- LICENSE.txt
|
24
|
+
- README.md
|
25
|
+
- Rakefile
|
26
|
+
- bin/console
|
27
|
+
- bin/setup
|
28
|
+
- lib/open3.rb
|
29
|
+
- lib/open3/version.rb
|
30
|
+
- open3.gemspec
|
31
|
+
homepage: https://github.com/ruby/open3
|
32
|
+
licenses:
|
33
|
+
- BSD-2-Clause
|
34
|
+
metadata:
|
35
|
+
homepage_uri: https://github.com/ruby/open3
|
36
|
+
source_code_uri: https://github.com/ruby/open3
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options: []
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
requirements: []
|
52
|
+
rubygems_version: 3.0.3
|
53
|
+
signing_key:
|
54
|
+
specification_version: 4
|
55
|
+
summary: Popen, but with stderr, too
|
56
|
+
test_files: []
|