subprocess 1.1.0 → 1.5.4
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/README.md +21 -20
- data/lib/subprocess.rb +282 -180
- data/lib/subprocess/version.rb +1 -1
- data/rbi/subprocess.rbi +312 -0
- metadata +33 -28
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 03feecd6b5ca0a66f4c795071be9ed48d4801ac8735e69660ff7e87b05f1e47d
|
4
|
+
data.tar.gz: 0dad63e1568ca3a477471b98b912ee0528963d80d1ee8646813b2efa7429d429
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0d5cc41f0a7d716cb87c6e70c25b4892905b12d2c55727e29af281bc8e66f46be68a6a76fc263ac1ed1a8fd709288b3101dcd0d2ed19aac36c94d9de133ea171
|
7
|
+
data.tar.gz: 25b247b27b30ce8dc4cf98670adb415f82bc4e4ce64ba5e410307ba194efd516c13af19ded736568a2e95ab88bacda16a46b626230be45f2a948db7e7876ad7e
|
data/README.md
CHANGED
@@ -1,15 +1,8 @@
|
|
1
|
-
Subprocess
|
2
|
-
==========
|
1
|
+
# Subprocess [](https://travis-ci.org/stripe/subprocess) [](http://rubydoc.info/github/stripe/subprocess/Subprocess)
|
3
2
|
|
4
3
|

|
5
4
|
|
6
|
-
A
|
7
|
-
|
8
|
-
Many thanks to [Bram Swenson][bram], the author of the old [subprocess][old]
|
9
|
-
gem, for graciously letting us use the name.
|
10
|
-
|
11
|
-
[bram]: https://github.com/bramswenson
|
12
|
-
[old]: https://github.com/bramswenson/subprocess
|
5
|
+
A solid subprocess library for ruby, inspired by python's.
|
13
6
|
|
14
7
|
Installation
|
15
8
|
------------
|
@@ -22,20 +15,10 @@ You can also build `subprocess` from source by running:
|
|
22
15
|
|
23
16
|
$ gem build subprocess.gemspec
|
24
17
|
|
25
|
-
|
26
18
|
Usage
|
27
19
|
-----
|
28
20
|
|
29
|
-
|
30
|
-
equally well to this gem as well. While there are a few places when our
|
31
|
-
semantics differs from Python's, users of the Python module should largely feel
|
32
|
-
at home using `subprocess`. We have attempted to [document][rubydoc] all of the
|
33
|
-
differences, but if we have missed something, please file an issue.
|
34
|
-
|
35
|
-
[python]: http://docs.python.org/library/subprocess.html
|
36
|
-
[rubydoc]: http://rubydoc.info/github/stripe/subprocess
|
37
|
-
|
38
|
-
A few examples:
|
21
|
+
Full documentation is on [RubyDoc][rubydoc]. A few examples:
|
39
22
|
|
40
23
|
```ruby
|
41
24
|
require 'subprocess'
|
@@ -72,3 +55,21 @@ http://upload.wikimedia.org/wikipedia/commons/3/3e/Unshorn_alpaca_grazing.jpg
|
|
72
55
|
EMAIL
|
73
56
|
end
|
74
57
|
```
|
58
|
+
|
59
|
+
Most of the documentation for Python's [subprocess][python] module applies
|
60
|
+
equally well to this gem as well. While there are a few places when our
|
61
|
+
semantics differs from Python's, users of the Python module should largely feel
|
62
|
+
at home using `subprocess`. We have attempted to [document][rubydoc] all of the
|
63
|
+
differences, but if we have missed something, please file an issue.
|
64
|
+
|
65
|
+
[python]: http://docs.python.org/library/subprocess.html
|
66
|
+
[rubydoc]: http://rubydoc.info/github/stripe/subprocess/Subprocess
|
67
|
+
|
68
|
+
Acknowledgements
|
69
|
+
----------------
|
70
|
+
|
71
|
+
Many thanks to [Bram Swenson][bram], the author of the old [subprocess][old]
|
72
|
+
gem, for graciously letting us use the name.
|
73
|
+
|
74
|
+
[bram]: https://github.com/bramswenson
|
75
|
+
[old]: https://github.com/bramswenson/subprocess
|
data/lib/subprocess.rb
CHANGED
@@ -16,7 +16,13 @@ module Subprocess
|
|
16
16
|
|
17
17
|
# An alias for `Process.new`. Mostly here to better emulate the Python API.
|
18
18
|
#
|
19
|
+
# @param [Array<String>] cmd See {Process#initialize}
|
20
|
+
# @param [Hash] opts See {Process#initialize}
|
21
|
+
# @yield [process] See {Process#initialize}
|
22
|
+
# @yieldparam process [Process] See {Process#initialize}
|
19
23
|
# @return [Process] A process with the given arguments
|
24
|
+
#
|
25
|
+
# @see Process#initialize
|
20
26
|
def self.popen(cmd, opts={}, &blk)
|
21
27
|
Process.new(cmd, opts, &blk)
|
22
28
|
end
|
@@ -28,9 +34,14 @@ module Subprocess
|
|
28
34
|
# fills up, as neither file descriptor will be read from. To avoid this, use
|
29
35
|
# {Process#communicate} from a passed block.
|
30
36
|
#
|
37
|
+
# @param [Array<String>] cmd See {Process#initialize}
|
38
|
+
# @param [Hash] opts See {Process#initialize}
|
39
|
+
# @yield [process] See {Process#initialize}
|
40
|
+
# @yieldparam process [Process] See {Process#initialize}
|
41
|
+
#
|
31
42
|
# @return [::Process::Status] The exit status of the process
|
32
43
|
#
|
33
|
-
# @see
|
44
|
+
# @see Process#initialize
|
34
45
|
def self.call(cmd, opts={}, &blk)
|
35
46
|
Process.new(cmd, opts, &blk).wait
|
36
47
|
end
|
@@ -58,11 +69,16 @@ module Subprocess
|
|
58
69
|
# fills up, as neither file descriptor will be read from. To avoid this, use
|
59
70
|
# {Process#communicate} from a passed block.
|
60
71
|
#
|
72
|
+
# @param [Array<String>] cmd See {Process#initialize}
|
73
|
+
# @param [Hash] opts See {Process#initialize}
|
74
|
+
# @yield [process] See {Process#initialize}
|
75
|
+
# @yieldparam process [Process] See {Process#initialize}
|
76
|
+
#
|
61
77
|
# @raise [NonZeroExit] if the process returned a non-zero exit status (i.e.,
|
62
78
|
# was terminated with an error or was killed by a signal)
|
63
79
|
# @return [::Process::Status] The exit status of the process
|
64
80
|
#
|
65
|
-
# @see
|
81
|
+
# @see Process#initialize
|
66
82
|
def self.check_call(cmd, opts={}, &blk)
|
67
83
|
status = Process.new(cmd, opts, &blk).wait
|
68
84
|
raise NonZeroExit.new(cmd, status) unless status.success?
|
@@ -70,16 +86,21 @@ module Subprocess
|
|
70
86
|
end
|
71
87
|
|
72
88
|
# Like {Subprocess::check_call}, but return the contents of `stdout`, much
|
73
|
-
# like
|
89
|
+
# like {::Kernel#system}.
|
74
90
|
#
|
75
91
|
# @example Get the system load
|
76
92
|
# system_load = Subprocess.check_output(['uptime']).split(' ').last(3)
|
77
93
|
#
|
94
|
+
# @param [Array<String>] cmd See {Process#initialize}
|
95
|
+
# @param [Hash] opts See {Process#initialize}
|
96
|
+
# @yield [process] See {Process#initialize}
|
97
|
+
# @yieldparam process [Process] See {Process#initialize}
|
98
|
+
#
|
78
99
|
# @raise [NonZeroExit] if the process returned a non-zero exit status (i.e.,
|
79
100
|
# was terminated with an error or was killed by a signal)
|
80
101
|
# @return [String] The contents of `stdout`
|
81
102
|
#
|
82
|
-
# @see
|
103
|
+
# @see Process#initialize
|
83
104
|
def self.check_output(cmd, opts={}, &blk)
|
84
105
|
opts[:stdout] = PIPE
|
85
106
|
child = Process.new(cmd, opts, &blk)
|
@@ -107,17 +128,7 @@ module Subprocess
|
|
107
128
|
# been according to the usual exit status convention
|
108
129
|
sig_num = status.exitstatus - 128
|
109
130
|
|
110
|
-
|
111
|
-
if Signal.respond_to?(:signame)
|
112
|
-
# ruby 2.0 way
|
113
|
-
sig_name = Signal.signame(sig_num)
|
114
|
-
elsif Signal.list.respond_to?(:key)
|
115
|
-
# ruby 1.9 way
|
116
|
-
sig_name = Signal.list.key(sig_num)
|
117
|
-
else
|
118
|
-
# ruby 1.8 way
|
119
|
-
sig_name = Signal.list.index(sig_num)
|
120
|
-
end
|
131
|
+
sig_name = Signal.signame(sig_num)
|
121
132
|
|
122
133
|
if sig_name
|
123
134
|
parts << "(maybe SIG#{sig_name})"
|
@@ -140,16 +151,17 @@ module Subprocess
|
|
140
151
|
|
141
152
|
# Error class representing a process's abnormal exit.
|
142
153
|
class NonZeroExit < StandardError
|
143
|
-
#
|
144
|
-
#
|
145
|
-
#
|
146
|
-
#
|
147
|
-
#
|
148
|
-
#
|
149
|
-
#
|
150
|
-
|
151
|
-
|
152
|
-
|
154
|
+
# @note This is intended only for use in user-facing error messages. In
|
155
|
+
# particular, no shell quoting of any sort is performed when
|
156
|
+
# constructing this string, meaning that blindly running it in a shell
|
157
|
+
# might have different semantics than the original command.
|
158
|
+
#
|
159
|
+
# @return [String] The command and arguments for the process that exited
|
160
|
+
# abnormally.
|
161
|
+
attr_reader :command
|
162
|
+
|
163
|
+
# @return [::Process::Status] The Ruby status object returned by `waitpid`
|
164
|
+
attr_reader :status
|
153
165
|
|
154
166
|
# Return an instance of {NonZeroExit}.
|
155
167
|
#
|
@@ -171,42 +183,63 @@ module Subprocess
|
|
171
183
|
end
|
172
184
|
end
|
173
185
|
|
186
|
+
# Error class representing a timeout during a call to `communicate`
|
187
|
+
class CommunicateTimeout < StandardError
|
188
|
+
# @return [String] Content read from stdout before the timeout
|
189
|
+
attr_reader :stdout
|
190
|
+
|
191
|
+
# @return [String] Content read from stderr before the timeout
|
192
|
+
attr_reader :stderr
|
193
|
+
|
194
|
+
# @param [Array<String>] cmd
|
195
|
+
# @param [String] stdout
|
196
|
+
# @param [String] stderr
|
197
|
+
def initialize(cmd, stdout, stderr)
|
198
|
+
@stdout = stdout
|
199
|
+
@stderr = stderr
|
200
|
+
|
201
|
+
super("Timeout communicating with `#{cmd.join(' ')}`")
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
174
205
|
# A child process. The preferred way of spawning a subprocess is through the
|
175
206
|
# functions on {Subprocess} (especially {Subprocess::check_call} and
|
176
207
|
# {Subprocess::check_output}).
|
177
208
|
class Process
|
178
|
-
#
|
179
|
-
|
180
|
-
|
181
|
-
#
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
#
|
188
|
-
|
189
|
-
|
190
|
-
#
|
191
|
-
|
192
|
-
|
193
|
-
|
209
|
+
# @return [IO] The `IO` that is connected to this process's `stdin`.
|
210
|
+
attr_reader :stdin
|
211
|
+
|
212
|
+
# @return [IO] The `IO` that is connected to this process's `stdout`.
|
213
|
+
attr_reader :stdout
|
214
|
+
|
215
|
+
# @return [IO] The `IO` that is connected to this process's `stderr`.
|
216
|
+
attr_reader :stderr
|
217
|
+
|
218
|
+
# @return [Array<String>] The command this process was invoked with.
|
219
|
+
attr_reader :command
|
220
|
+
|
221
|
+
# @return [Integer] The process ID of the spawned process.
|
222
|
+
attr_reader :pid
|
223
|
+
|
224
|
+
# @return [::Process::Status] The exit status code of the process. Only
|
225
|
+
# set after the process has exited.
|
226
|
+
attr_reader :status
|
194
227
|
|
195
228
|
# Create a new process.
|
196
229
|
#
|
197
230
|
# @param [Array<String>] cmd The command to run and its arguments (in the
|
198
231
|
# style of an `argv` array). Unlike Python's subprocess module, `cmd`
|
199
|
-
#
|
232
|
+
# cannot be a String.
|
200
233
|
#
|
201
|
-
# @option opts [IO,
|
234
|
+
# @option opts [IO, Integer, String, Subprocess::PIPE, nil] :stdin The `IO`,
|
202
235
|
# file descriptor number, or file name to use for the process's standard
|
203
236
|
# input. If the magic value {Subprocess::PIPE} is passed, a new pipe will
|
204
237
|
# be opened.
|
205
|
-
# @option opts [IO,
|
238
|
+
# @option opts [IO, Integer, String, Subprocess::PIPE, nil] :stdout The `IO`,
|
206
239
|
# file descriptor number, or file name to use for the process's standard
|
207
240
|
# output. If the magic value {Subprocess::PIPE} is passed, a pipe will be
|
208
241
|
# opened and attached to the process.
|
209
|
-
# @option opts [IO,
|
242
|
+
# @option opts [IO, Integer, String, Subprocess::PIPE, Subprocess::STDOUT,
|
210
243
|
# nil] :stderr The `IO`, file descriptor number, or file name to use for
|
211
244
|
# the process's standard error. If the special value {Subprocess::PIPE} is
|
212
245
|
# passed, a pipe will be opened and attached to the process. If the
|
@@ -217,16 +250,15 @@ module Subprocess
|
|
217
250
|
# child process.
|
218
251
|
# @option opts [Hash<String, String>] :env The environment to use in the
|
219
252
|
# child process.
|
220
|
-
# @option opts [Array<
|
253
|
+
# @option opts [Array<Integer>] :retain_fds An array of file descriptor
|
221
254
|
# numbers that should not be closed before executing the child process.
|
222
255
|
# Note that, unlike Python (which has :close_fds defaulting to false), all
|
223
256
|
# file descriptors not specified here will be closed.
|
257
|
+
# @option opts [Hash] :exec_opts A hash that will be merged into the options
|
258
|
+
# hash of the call to {::Kernel#exec}.
|
224
259
|
#
|
225
260
|
# @option opts [Proc] :preexec_fn A function that will be called in the
|
226
|
-
# child process immediately before executing `cmd`.
|
227
|
-
# actually close file descriptors, but instead set them to auto-close on
|
228
|
-
# `exec` (using `FD_CLOEXEC`), so your application will probably continue
|
229
|
-
# to behave as expected.
|
261
|
+
# child process immediately before executing `cmd`.
|
230
262
|
#
|
231
263
|
# @yield [process] Yields the just-spawned {Process} to the optional block.
|
232
264
|
# This occurs after all of {Process}'s error handling has been completed,
|
@@ -234,7 +266,8 @@ module Subprocess
|
|
234
266
|
# in conjunction with {Subprocess::check_call}.
|
235
267
|
# @yieldparam process [Process] The process that was just spawned.
|
236
268
|
def initialize(cmd, opts={}, &blk)
|
237
|
-
raise ArgumentError, "cmd must be an Array" unless Array === cmd
|
269
|
+
raise ArgumentError, "cmd must be an Array of strings" unless Array === cmd
|
270
|
+
raise ArgumentError, "cmd cannot be empty" if cmd.empty?
|
238
271
|
|
239
272
|
@command = cmd
|
240
273
|
|
@@ -253,69 +286,6 @@ module Subprocess
|
|
253
286
|
|
254
287
|
@pid = fork do
|
255
288
|
begin
|
256
|
-
require 'fcntl'
|
257
|
-
|
258
|
-
FileUtils.cd(opts[:cwd]) if opts[:cwd]
|
259
|
-
|
260
|
-
# The only way to mark an fd as CLOEXEC in ruby is to create an IO
|
261
|
-
# object wrapping it. In 1.8, however, there's no way to create that
|
262
|
-
# IO without it believing it owns the underlying fd, s.t. it will
|
263
|
-
# close the fd if the IO is GC'd before the exec. Since we don't want
|
264
|
-
# that, we stash a list of these IO objects to prevent them from
|
265
|
-
# getting GC'd, since we are about to exec, which will clean
|
266
|
-
# everything up anyways.
|
267
|
-
fds = []
|
268
|
-
|
269
|
-
# We have a whole ton of file descriptors that we don't want leaking
|
270
|
-
# into the child. Set them all to close when we exec away.
|
271
|
-
#
|
272
|
-
# Ruby 1.9+ note: exec has a :close_others argument (and 2.0 closes
|
273
|
-
# FDs by default). When we stop supporting Ruby 1.8, all of this can
|
274
|
-
# go away.
|
275
|
-
if File.directory?("/dev/fd")
|
276
|
-
# On many modern UNIX-y systems, we can perform an optimization by
|
277
|
-
# looking through /dev/fd, which is a sparse listing of all the
|
278
|
-
# descriptors we have open. This allows us to avoid an expensive
|
279
|
-
# linear scan.
|
280
|
-
Dir.foreach("/dev/fd") do |file|
|
281
|
-
fd = file.to_i
|
282
|
-
if file.start_with?('.') || fd < 3 || retained_fds.include?(fd)
|
283
|
-
next
|
284
|
-
end
|
285
|
-
begin
|
286
|
-
fds << mark_fd_cloexec(fd)
|
287
|
-
rescue Errno::EBADF
|
288
|
-
# The fd might have been closed by now; that's peaceful.
|
289
|
-
end
|
290
|
-
end
|
291
|
-
else
|
292
|
-
# This is the big hammer. There's not really a good way of doing
|
293
|
-
# this comprehensively across all platforms without just trying them
|
294
|
-
# all. We only go up to the soft limit here. If you've been messing
|
295
|
-
# with the soft limit, we might miss a few. Also, on OSX (perhaps
|
296
|
-
# BSDs in general?), where the soft limit means something completely
|
297
|
-
# different.
|
298
|
-
special = [@child_stdin, @child_stdout, @child_stderr].compact
|
299
|
-
special = Hash[special.map { |f| [f.fileno, f] }]
|
300
|
-
3.upto(::Process.getrlimit(::Process::RLIMIT_NOFILE).first) do |fd|
|
301
|
-
next if retained_fds.include?(fd)
|
302
|
-
begin
|
303
|
-
# I don't know why we need to do this, but OSX started freaking
|
304
|
-
# out when trying to dup2 below if FD_CLOEXEC had been set on a
|
305
|
-
# fresh IO instance referring to the same underlying file
|
306
|
-
# descriptor as what we were trying to dup2 from.
|
307
|
-
if special[fd]
|
308
|
-
special[fd].fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
309
|
-
else
|
310
|
-
fds << mark_fd_cloexec(fd)
|
311
|
-
end
|
312
|
-
rescue Errno::EBADF # Ignore FDs that don't exist
|
313
|
-
end
|
314
|
-
end
|
315
|
-
end
|
316
|
-
|
317
|
-
# dup2 the correct descriptors into place. Note that this clears the
|
318
|
-
# FD_CLOEXEC flag on the new file descriptors (but not the old ones).
|
319
289
|
::STDIN.reopen(@child_stdin) if @child_stdin
|
320
290
|
::STDOUT.reopen(@child_stdout) if @child_stdout
|
321
291
|
if opts[:stderr] == STDOUT
|
@@ -327,25 +297,44 @@ module Subprocess
|
|
327
297
|
# Set up a new environment if we're requested to do so.
|
328
298
|
if opts[:env]
|
329
299
|
ENV.clear
|
330
|
-
|
300
|
+
begin
|
301
|
+
ENV.update(opts[:env])
|
302
|
+
rescue TypeError => e
|
303
|
+
raise ArgumentError, "`env` option must be a hash where all keys and values are strings (#{e})"
|
304
|
+
end
|
331
305
|
end
|
332
306
|
|
333
307
|
# Call the user back, maybe?
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
if cmd.length == 1
|
341
|
-
args = ["'" + cmd[0].gsub("'", "\\'") + "'"]
|
308
|
+
if opts[:preexec_fn]
|
309
|
+
if opts[:cwd]
|
310
|
+
Dir.chdir(opts[:cwd], &opts[:preexec_fn])
|
311
|
+
else
|
312
|
+
opts[:preexec_fn].call
|
313
|
+
end
|
342
314
|
end
|
315
|
+
|
316
|
+
options = {close_others: true}.merge(opts.fetch(:exec_opts, {}))
|
343
317
|
if opts[:retain_fds]
|
344
|
-
|
345
|
-
|
346
|
-
|
318
|
+
retained_fds.each { |fd| options[fd] = fd }
|
319
|
+
end
|
320
|
+
if opts[:cwd]
|
321
|
+
# We use the chdir option to `exec` since wrapping the
|
322
|
+
# `exec` in a Dir.chdir block caused these sporadic errors on macOS:
|
323
|
+
# Too many open files - getcwd (Errno::EMFILE)
|
324
|
+
options[:chdir] = opts[:cwd]
|
325
|
+
end
|
326
|
+
|
327
|
+
begin
|
328
|
+
# Ruby's Kernel#exec will call an exec(3) variant if called with two
|
329
|
+
# or more arguments, but when called with just a single argument will
|
330
|
+
# spawn a subshell with that argument as the command. Since we always
|
331
|
+
# want to call exec(3), we use the third exec form, which passes a
|
332
|
+
# [cmdname, argv0] array as its first argument and never invokes a
|
333
|
+
# subshell.
|
334
|
+
exec([cmd[0], cmd[0]], *cmd[1..-1], options)
|
335
|
+
rescue TypeError => e
|
336
|
+
raise ArgumentError, "cmd must be an Array of strings (#{e})"
|
347
337
|
end
|
348
|
-
exec(*args)
|
349
338
|
|
350
339
|
rescue Exception => e
|
351
340
|
# Dump all errors up to the parent through the control socket
|
@@ -360,12 +349,13 @@ module Subprocess
|
|
360
349
|
# Meanwhile, in the parent process...
|
361
350
|
|
362
351
|
# First, let's close some things we shouldn't have access to
|
363
|
-
|
364
|
-
|
365
|
-
|
352
|
+
@child_stdin.close if our_fd?(opts[:stdin])
|
353
|
+
@child_stdout.close if our_fd?(opts[:stdout])
|
354
|
+
@child_stderr.close if our_fd?(opts[:stderr])
|
355
|
+
control_w.close
|
366
356
|
|
367
357
|
# Any errors during the spawn process? We'll get past this point when the
|
368
|
-
# child execs and the OS closes control_w
|
358
|
+
# child execs and the OS closes control_w
|
369
359
|
begin
|
370
360
|
e = Marshal.load(control_r)
|
371
361
|
e = "Unknown Failure" unless e.is_a?(Exception) || e.is_a?(String)
|
@@ -407,7 +397,7 @@ module Subprocess
|
|
407
397
|
# condition (`EOFError` or `EPIPE`).
|
408
398
|
def drain_fd(fd, buf=nil)
|
409
399
|
loop do
|
410
|
-
tmp = fd.read_nonblock(4096)
|
400
|
+
tmp = fd.read_nonblock(4096).force_encoding(fd.external_encoding)
|
411
401
|
buf << tmp unless buf.nil?
|
412
402
|
end
|
413
403
|
rescue EOFError, Errno::EPIPE
|
@@ -418,40 +408,48 @@ module Subprocess
|
|
418
408
|
false
|
419
409
|
end
|
420
410
|
|
421
|
-
# Write the (optional) input to the process's `stdin
|
422
|
-
#
|
423
|
-
#
|
411
|
+
# Write the (optional) input to the process's `stdin` and read the contents of
|
412
|
+
# `stdout` and `stderr`. If a block is provided, stdout and stderr are yielded as they
|
413
|
+
# are read. Otherwise they are buffered in memory and returned when the process
|
414
|
+
# exits. Do this all using `IO::select`, so we don't deadlock due to full pipe
|
415
|
+
# buffers.
|
424
416
|
#
|
425
417
|
# This is only really useful if you set some of `:stdin`, `:stdout`, and
|
426
418
|
# `:stderr` to {Subprocess::PIPE}.
|
427
419
|
#
|
428
420
|
# @param [String] input A string to feed to the child's standard input.
|
429
|
-
# @
|
421
|
+
# @param [Numeric] timeout_s Raise {Subprocess::CommunicateTimeout} if communicate
|
422
|
+
# does not finish after timeout_s seconds.
|
423
|
+
# @yieldparam [String] stdout Data read from stdout since the last yield
|
424
|
+
# @yieldparam [String] stderr Data read from stderr since the last yield
|
425
|
+
# @return [Array(String, String), nil] An array of two elements: the data read from the
|
430
426
|
# child's standard output and standard error, respectively.
|
431
|
-
|
427
|
+
# Returns nil if a block is provided.
|
428
|
+
def communicate(input=nil, timeout_s=nil)
|
432
429
|
raise ArgumentError if !input.nil? && @stdin.nil?
|
433
430
|
|
434
431
|
stdout, stderr = "", ""
|
432
|
+
|
435
433
|
input = input.dup unless input.nil?
|
436
434
|
|
437
435
|
@stdin.close if (input.nil? || input.empty?) && !@stdin.nil?
|
438
436
|
|
439
|
-
|
440
|
-
|
441
|
-
|
437
|
+
timeout_at = Time.now + timeout_s if timeout_s
|
438
|
+
|
439
|
+
self.class.catching_sigchld(pid) do |global_read, self_read|
|
440
|
+
wait_r = [@stdout, @stderr, self_read, global_read].compact
|
442
441
|
wait_w = [input && @stdin].compact
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
poll
|
442
|
+
done = false
|
443
|
+
while !done
|
444
|
+
# If the process has exited, we want to drain any remaining output before returning
|
445
|
+
if poll
|
446
|
+
ready_r = wait_r - [self_read, global_read]
|
447
|
+
ready_w = []
|
448
|
+
done = true
|
449
|
+
else
|
450
|
+
ready_r, ready_w = select_until(wait_r, wait_w, [], timeout_at)
|
451
|
+
raise CommunicateTimeout.new(@command, stdout, stderr) if ready_r.nil?
|
452
|
+
end
|
455
453
|
|
456
454
|
if ready_r.include?(@stdout)
|
457
455
|
if drain_fd(@stdout, stdout)
|
@@ -465,6 +463,13 @@ module Subprocess
|
|
465
463
|
end
|
466
464
|
end
|
467
465
|
|
466
|
+
if ready_r.include?(global_read)
|
467
|
+
if drain_fd(global_read)
|
468
|
+
raise "Unexpected internal error -- someone closed the global self-pipe!"
|
469
|
+
end
|
470
|
+
self.class.wakeup_sigchld
|
471
|
+
end
|
472
|
+
|
468
473
|
if ready_r.include?(self_read)
|
469
474
|
if drain_fd(self_read)
|
470
475
|
raise "Unexpected internal error -- someone closed our self-pipe!"
|
@@ -472,10 +477,24 @@ module Subprocess
|
|
472
477
|
end
|
473
478
|
|
474
479
|
if ready_w.include?(@stdin)
|
480
|
+
written = 0
|
475
481
|
begin
|
476
482
|
written = @stdin.write_nonblock(input)
|
477
483
|
rescue EOFError # Maybe I shouldn't catch this...
|
478
484
|
rescue Errno::EINTR
|
485
|
+
rescue IO::WaitWritable
|
486
|
+
# On OS X, a pipe can raise EAGAIN even after select indicates
|
487
|
+
# that it is writable. Once the process consumes from the pipe,
|
488
|
+
# the next write should succeed and we should make forward progress.
|
489
|
+
# Until then, treat this as not writing any bytes and continue looping.
|
490
|
+
# For details see: https://github.com/stripe/subprocess/pull/22
|
491
|
+
nil
|
492
|
+
rescue Errno::EPIPE
|
493
|
+
# The other side of the pipe closed before we could
|
494
|
+
# write all of our input. This can happen if the
|
495
|
+
# process exits prematurely.
|
496
|
+
@stdin.close
|
497
|
+
wait_w.delete(@stdin)
|
479
498
|
end
|
480
499
|
input[0...written] = ''
|
481
500
|
if input.empty?
|
@@ -484,29 +503,41 @@ module Subprocess
|
|
484
503
|
end
|
485
504
|
end
|
486
505
|
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
506
|
+
if block_given? && !(stderr.empty? && stdout.empty?)
|
507
|
+
yield stdout, stderr
|
508
|
+
stdout, stderr = "", ""
|
509
|
+
end
|
491
510
|
end
|
492
511
|
end
|
493
512
|
|
494
513
|
wait
|
495
514
|
|
496
|
-
|
515
|
+
if block_given?
|
516
|
+
nil
|
517
|
+
else
|
518
|
+
[stdout, stderr]
|
519
|
+
end
|
497
520
|
end
|
498
521
|
|
499
522
|
# Does exactly what it says on the box.
|
500
523
|
#
|
501
|
-
# @param [String, Symbol,
|
524
|
+
# @param [String, Symbol, Integer] signal The signal to send to the child
|
502
525
|
# process. Accepts all the same arguments as Ruby's built-in
|
503
526
|
# {::Process::kill}, for instance a string like "INT" or "SIGINT", or a
|
504
527
|
# signal number like 2.
|
528
|
+
#
|
529
|
+
# @return [Integer] See {::Process.kill}
|
530
|
+
#
|
531
|
+
# @see ::Process.kill
|
505
532
|
def send_signal(signal)
|
506
533
|
::Process.kill(signal, pid)
|
507
534
|
end
|
508
535
|
|
509
536
|
# Sends `SIGTERM` to the process.
|
537
|
+
#
|
538
|
+
# @return [Integer] See {send_signal}
|
539
|
+
#
|
540
|
+
# @see send_signal
|
510
541
|
def terminate
|
511
542
|
send_signal("TERM")
|
512
543
|
end
|
@@ -517,16 +548,17 @@ module Subprocess
|
|
517
548
|
# "mine" is only non-nil in the case of a pipe (in fact, we just return a
|
518
549
|
# list of length one, since ruby will unpack nils from missing list items).
|
519
550
|
#
|
520
|
-
#
|
521
|
-
#
|
551
|
+
# @param [IO, Integer, String, nil] fd
|
552
|
+
# @param [String] mode
|
553
|
+
# @return [Array<IO>]
|
522
554
|
def parse_fd(fd, mode)
|
523
555
|
fds = case fd
|
524
556
|
when PIPE
|
525
557
|
IO.pipe
|
526
558
|
when IO
|
527
|
-
[fd
|
559
|
+
[fd]
|
528
560
|
when Integer
|
529
|
-
[IO.new(fd, mode)
|
561
|
+
[IO.new(fd, mode)]
|
530
562
|
when String
|
531
563
|
[File.open(fd, mode)]
|
532
564
|
when nil
|
@@ -538,42 +570,106 @@ module Subprocess
|
|
538
570
|
mode == 'r' ? fds : fds.reverse
|
539
571
|
end
|
540
572
|
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
573
|
+
# The pair to parse_fd, returns whether or not the file descriptor was
|
574
|
+
# opened by us (and therefore should be closed by us).
|
575
|
+
#
|
576
|
+
# @param [IO, Integer, String, nil] fd
|
577
|
+
# @return [Boolean]
|
578
|
+
def our_fd?(fd)
|
579
|
+
case fd
|
580
|
+
when PIPE, String
|
581
|
+
true
|
582
|
+
else
|
583
|
+
false
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
587
|
+
# Call IO.select timing out at Time `timeout_at`. If `timeout_at` is nil, never times out.
|
588
|
+
#
|
589
|
+
# @param [Array<IO>, nil] read_array
|
590
|
+
# @param [Array<IO>, nil] write_array
|
591
|
+
# @param [Array<IO>, nil] err_array
|
592
|
+
# @param [Integer, Float, nil] timeout_at
|
593
|
+
# @return [Array<Array<IO>>, nil]
|
594
|
+
def select_until(read_array, write_array, err_array, timeout_at)
|
595
|
+
if !timeout_at
|
596
|
+
return IO.select(read_array, write_array, err_array)
|
597
|
+
end
|
598
|
+
|
599
|
+
remaining = (timeout_at - Time.now)
|
600
|
+
return nil if remaining <= 0
|
601
|
+
|
602
|
+
IO.select(read_array, write_array, err_array, remaining)
|
549
603
|
end
|
550
604
|
|
551
605
|
@sigchld_mutex = Mutex.new
|
552
606
|
@sigchld_fds = {}
|
553
607
|
@sigchld_old_handler = nil
|
608
|
+
@sigchld_global_write = nil
|
609
|
+
@sigchld_global_read = nil
|
610
|
+
@sigchld_pipe_pid = nil
|
611
|
+
|
612
|
+
# @return [void]
|
613
|
+
def self.handle_sigchld
|
614
|
+
# We'd like to just notify everything in `@sigchld_fds`, but
|
615
|
+
# ruby signal handlers are not executed atomically with respect
|
616
|
+
# to other Ruby threads, so reading it is racy. We can't grab
|
617
|
+
# `@sigchld_mutex`, because signal execution blocks the main
|
618
|
+
# thread, and so we'd deadlock if the main thread currently
|
619
|
+
# holds it.
|
620
|
+
#
|
621
|
+
# Instead, we keep a long-lived notify self-pipe that we select
|
622
|
+
# on inside `communicate`, and we task `communicate` with
|
623
|
+
# grabbing the lock and fanning out the wakeups.
|
624
|
+
begin
|
625
|
+
@sigchld_global_write.write_nonblock("\x00")
|
626
|
+
rescue Errno::EWOULDBLOCK, Errno::EAGAIN
|
627
|
+
nil # ignore
|
628
|
+
end
|
629
|
+
end
|
554
630
|
|
555
631
|
# Wake up everyone. We can't tell who we should wake up without `wait`ing,
|
556
632
|
# and we want to let the process itself do that. In practice, we're not
|
557
633
|
# likely to have that many in-flight subprocesses, so this is probably not a
|
558
634
|
# big deal.
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
635
|
+
# @return [void]
|
636
|
+
def self.wakeup_sigchld
|
637
|
+
@sigchld_mutex.synchronize do
|
638
|
+
@sigchld_fds.values.each do |fd|
|
639
|
+
begin
|
640
|
+
fd.write_nonblock("\x00")
|
641
|
+
rescue Errno::EWOULDBLOCK, Errno::EAGAIN
|
642
|
+
# If the pipe is full, the other end will be woken up
|
643
|
+
# regardless when it next reads, so it's fine to skip the
|
644
|
+
# write (the pipe is a wakeup channel, and doesn't contain
|
645
|
+
# meaningful data).
|
646
|
+
end
|
564
647
|
end
|
565
648
|
end
|
566
649
|
end
|
567
650
|
|
651
|
+
# @param [Integer] pid
|
652
|
+
# @param [IO] fd
|
653
|
+
# @return [void]
|
568
654
|
def self.register_pid(pid, fd)
|
569
655
|
@sigchld_mutex.synchronize do
|
570
656
|
@sigchld_fds[pid] = fd
|
571
657
|
if @sigchld_fds.length == 1
|
658
|
+
if @sigchld_global_write.nil? || @sigchld_pipe_pid != ::Process.pid
|
659
|
+
# Check the PID so that if we fork we will re-open the
|
660
|
+
# pipe. It's important that a fork parent and child don't
|
661
|
+
# share this pipe, because if they do they risk stealing
|
662
|
+
# each others' wakeups.
|
663
|
+
@sigchld_pipe_pid = ::Process.pid
|
664
|
+
@sigchld_global_read, @sigchld_global_write = IO.pipe
|
665
|
+
end
|
572
666
|
@sigchld_old_handler = Signal.trap('SIGCHLD') {handle_sigchld}
|
573
667
|
end
|
574
668
|
end
|
575
669
|
end
|
576
670
|
|
671
|
+
# @param [Integer] pid
|
672
|
+
# @return [void]
|
577
673
|
def self.unregister_pid(pid)
|
578
674
|
@sigchld_mutex.synchronize do
|
579
675
|
if @sigchld_fds.length == 1
|
@@ -583,11 +679,17 @@ module Subprocess
|
|
583
679
|
end
|
584
680
|
end
|
585
681
|
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
682
|
+
# @param [Integer] pid
|
683
|
+
# @return [void]
|
684
|
+
def self.catching_sigchld(pid)
|
685
|
+
IO.pipe do |self_read, self_write|
|
686
|
+
begin
|
687
|
+
register_pid(pid, self_write)
|
688
|
+
yield @sigchld_global_read, self_read
|
689
|
+
ensure
|
690
|
+
unregister_pid(pid)
|
691
|
+
end
|
692
|
+
end
|
591
693
|
end
|
592
694
|
end
|
593
695
|
end
|
data/lib/subprocess/version.rb
CHANGED
data/rbi/subprocess.rbi
ADDED
@@ -0,0 +1,312 @@
|
|
1
|
+
# typed: strong
|
2
|
+
|
3
|
+
# THIS FILE IS AUTOGENERATED. DO NOT EDIT.
|
4
|
+
# To regenerate from YARD comments:
|
5
|
+
#
|
6
|
+
# bundle exec rake sord
|
7
|
+
#
|
8
|
+
# A Ruby clone of Python's subprocess module.
|
9
|
+
#
|
10
|
+
# @see http://docs.python.org/2/library/subprocess.html
|
11
|
+
module ::Subprocess
|
12
|
+
PIPE = T.let(-1, T.untyped)
|
13
|
+
STDOUT = T.let(-2, T.untyped)
|
14
|
+
VERSION = T.let('1.5.4', T.untyped)
|
15
|
+
|
16
|
+
# An alias for `Process.new`. Mostly here to better emulate the Python API.
|
17
|
+
#
|
18
|
+
# _@param_ `cmd` — See {Process#initialize}
|
19
|
+
#
|
20
|
+
# _@param_ `opts` — See {Process#initialize}
|
21
|
+
#
|
22
|
+
# _@return_ — A process with the given arguments
|
23
|
+
#
|
24
|
+
# _@see_ `Process#initialize`
|
25
|
+
sig { params(cmd: T::Array[String], opts: T::Hash[T.untyped, T.untyped], blk: T.nilable(T.proc.params(process: Process).void)).returns(Process) }
|
26
|
+
def self.popen(cmd, opts = {}, &blk); end
|
27
|
+
|
28
|
+
# Call and wait for the return of a given process.
|
29
|
+
#
|
30
|
+
# _@param_ `cmd` — See {Process#initialize}
|
31
|
+
#
|
32
|
+
# _@param_ `opts` — See {Process#initialize}
|
33
|
+
#
|
34
|
+
# _@return_ — The exit status of the process
|
35
|
+
#
|
36
|
+
# _@note_ — If you call this function with `:stdout => PIPE` or `:stderr => PIPE`,
|
37
|
+
# this function will block indefinitely as soon as the OS's pipe buffer
|
38
|
+
# fills up, as neither file descriptor will be read from. To avoid this, use
|
39
|
+
# {Process#communicate} from a passed block.
|
40
|
+
#
|
41
|
+
# _@see_ `Process#initialize`
|
42
|
+
sig { params(cmd: T::Array[String], opts: T::Hash[T.untyped, T.untyped], blk: T.nilable(T.proc.params(process: Process).void)).returns(::Process::Status) }
|
43
|
+
def self.call(cmd, opts = {}, &blk); end
|
44
|
+
|
45
|
+
# Like {Subprocess::call}, except raise a {NonZeroExit} if the process did not
|
46
|
+
# terminate successfully.
|
47
|
+
#
|
48
|
+
# _@param_ `cmd` — See {Process#initialize}
|
49
|
+
#
|
50
|
+
# _@param_ `opts` — See {Process#initialize}
|
51
|
+
#
|
52
|
+
# _@return_ — The exit status of the process
|
53
|
+
#
|
54
|
+
# Grep a file for a string
|
55
|
+
# ```ruby
|
56
|
+
# Subprocess.check_call(%W{grep -q llama ~/favorite_animals})
|
57
|
+
# ```
|
58
|
+
#
|
59
|
+
# Communicate with a child process
|
60
|
+
# ```ruby
|
61
|
+
# Subprocess.check_call(%W{sendmail -t}, :stdin => Subprocess::PIPE) do |p|
|
62
|
+
# p.communicate <<-EMAIL
|
63
|
+
# From: alpaca@example.com
|
64
|
+
# To: llama@example.com
|
65
|
+
# Subject: I am so fluffy.
|
66
|
+
#
|
67
|
+
# SO FLUFFY!
|
68
|
+
# http://upload.wikimedia.org/wikipedia/commons/3/3e/Unshorn_alpaca_grazing.jpg
|
69
|
+
# EMAIL
|
70
|
+
# end
|
71
|
+
# ```
|
72
|
+
#
|
73
|
+
# _@note_ — If you call this function with `:stdout => PIPE` or `:stderr => PIPE`,
|
74
|
+
# this function will block indefinitely as soon as the OS's pipe buffer
|
75
|
+
# fills up, as neither file descriptor will be read from. To avoid this, use
|
76
|
+
# {Process#communicate} from a passed block.
|
77
|
+
#
|
78
|
+
# _@see_ `Process#initialize`
|
79
|
+
sig { params(cmd: T::Array[String], opts: T::Hash[T.untyped, T.untyped], blk: T.nilable(T.proc.params(process: Process).void)).returns(::Process::Status) }
|
80
|
+
def self.check_call(cmd, opts = {}, &blk); end
|
81
|
+
|
82
|
+
# Like {Subprocess::check_call}, but return the contents of `stdout`, much
|
83
|
+
# like {::Kernel#system}.
|
84
|
+
#
|
85
|
+
# _@param_ `cmd` — See {Process#initialize}
|
86
|
+
#
|
87
|
+
# _@param_ `opts` — See {Process#initialize}
|
88
|
+
#
|
89
|
+
# _@return_ — The contents of `stdout`
|
90
|
+
#
|
91
|
+
# Get the system load
|
92
|
+
# ```ruby
|
93
|
+
# system_load = Subprocess.check_output(['uptime']).split(' ').last(3)
|
94
|
+
# ```
|
95
|
+
#
|
96
|
+
# _@see_ `Process#initialize`
|
97
|
+
sig { params(cmd: T::Array[String], opts: T::Hash[T.untyped, T.untyped], blk: T.nilable(T.proc.params(process: Process).void)).returns(String) }
|
98
|
+
def self.check_output(cmd, opts = {}, &blk); end
|
99
|
+
|
100
|
+
# Print a human readable interpretation of a process exit status.
|
101
|
+
#
|
102
|
+
# _@param_ `status` — The status returned by `waitpid2`.
|
103
|
+
#
|
104
|
+
# _@param_ `convert_high_exit` — Whether to convert exit statuses greater than 128 into the usual convention for exiting after trapping a signal. (e.g. many programs will exit with status 130 after receiving a SIGINT / signal 2.)
|
105
|
+
#
|
106
|
+
# _@return_ — Text interpretation
|
107
|
+
sig { params(status: ::Process::Status, convert_high_exit: T::Boolean).returns(String) }
|
108
|
+
def self.status_to_s(status, convert_high_exit = true); end
|
109
|
+
|
110
|
+
# Error class representing a process's abnormal exit.
|
111
|
+
class NonZeroExit < StandardError
|
112
|
+
# Return an instance of {NonZeroExit}.
|
113
|
+
#
|
114
|
+
# _@param_ `cmd` — The command that returned a non-zero status.
|
115
|
+
#
|
116
|
+
# _@param_ `status` — The status returned by `waitpid`.
|
117
|
+
sig { params(cmd: T::Array[String], status: ::Process::Status).void }
|
118
|
+
def initialize(cmd, status); end
|
119
|
+
|
120
|
+
# _@return_ — The command and arguments for the process that exited
|
121
|
+
# abnormally.
|
122
|
+
#
|
123
|
+
# _@note_ — This is intended only for use in user-facing error messages. In
|
124
|
+
# particular, no shell quoting of any sort is performed when
|
125
|
+
# constructing this string, meaning that blindly running it in a shell
|
126
|
+
# might have different semantics than the original command.
|
127
|
+
sig { returns(String) }
|
128
|
+
attr_reader :command
|
129
|
+
|
130
|
+
# _@return_ — The Ruby status object returned by `waitpid`
|
131
|
+
sig { returns(::Process::Status) }
|
132
|
+
attr_reader :status
|
133
|
+
end
|
134
|
+
|
135
|
+
# Error class representing a timeout during a call to `communicate`
|
136
|
+
class CommunicateTimeout < StandardError
|
137
|
+
# _@param_ `cmd`
|
138
|
+
#
|
139
|
+
# _@param_ `stdout`
|
140
|
+
#
|
141
|
+
# _@param_ `stderr`
|
142
|
+
sig { params(cmd: T::Array[String], stdout: String, stderr: String).void }
|
143
|
+
def initialize(cmd, stdout, stderr); end
|
144
|
+
|
145
|
+
# _@return_ — Content read from stdout before the timeout
|
146
|
+
sig { returns(String) }
|
147
|
+
attr_reader :stdout
|
148
|
+
|
149
|
+
# _@return_ — Content read from stderr before the timeout
|
150
|
+
sig { returns(String) }
|
151
|
+
attr_reader :stderr
|
152
|
+
end
|
153
|
+
|
154
|
+
# A child process. The preferred way of spawning a subprocess is through the
|
155
|
+
# functions on {Subprocess} (especially {Subprocess::check_call} and
|
156
|
+
# {Subprocess::check_output}).
|
157
|
+
class Process
|
158
|
+
# Create a new process.
|
159
|
+
#
|
160
|
+
# _@param_ `cmd` — The command to run and its arguments (in the style of an `argv` array). Unlike Python's subprocess module, `cmd` cannot be a String.
|
161
|
+
sig { params(cmd: T::Array[String], opts: T::Hash[T.untyped, T.untyped], blk: T.nilable(T.proc.params(process: Process).void)).void }
|
162
|
+
def initialize(cmd, opts = {}, &blk); end
|
163
|
+
|
164
|
+
# Poll the child, setting (and returning) its status. If the child has not
|
165
|
+
# terminated, return nil and exit immediately
|
166
|
+
#
|
167
|
+
# _@return_ — The exit status of the process
|
168
|
+
sig { returns(T.nilable(::Process::Status)) }
|
169
|
+
def poll; end
|
170
|
+
|
171
|
+
# Wait for the child to return, setting and returning the status of the
|
172
|
+
# child.
|
173
|
+
#
|
174
|
+
# _@return_ — The exit status of the process
|
175
|
+
sig { returns(::Process::Status) }
|
176
|
+
def wait; end
|
177
|
+
|
178
|
+
# Do nonblocking reads from `fd`, appending all data read into `buf`.
|
179
|
+
#
|
180
|
+
# _@param_ `fd` — The file to read from.
|
181
|
+
#
|
182
|
+
# _@param_ `buf` — A buffer to append the read data to.
|
183
|
+
#
|
184
|
+
# _@return_ — Whether `fd` was closed due to an exceptional
|
185
|
+
# condition (`EOFError` or `EPIPE`).
|
186
|
+
sig { params(fd: IO, buf: T.nilable(String)).returns(T::Boolean) }
|
187
|
+
def drain_fd(fd, buf = nil); end
|
188
|
+
|
189
|
+
# Write the (optional) input to the process's `stdin` and read the contents of
|
190
|
+
# `stdout` and `stderr`. If a block is provided, stdout and stderr are yielded as they
|
191
|
+
# are read. Otherwise they are buffered in memory and returned when the process
|
192
|
+
# exits. Do this all using `IO::select`, so we don't deadlock due to full pipe
|
193
|
+
# buffers.
|
194
|
+
#
|
195
|
+
# This is only really useful if you set some of `:stdin`, `:stdout`, and
|
196
|
+
# `:stderr` to {Subprocess::PIPE}.
|
197
|
+
#
|
198
|
+
# _@param_ `input` — A string to feed to the child's standard input.
|
199
|
+
#
|
200
|
+
# _@param_ `timeout_s` — Raise {Subprocess::CommunicateTimeout} if communicate does not finish after timeout_s seconds.
|
201
|
+
#
|
202
|
+
# _@return_ — An array of two elements: the data read from the
|
203
|
+
# child's standard output and standard error, respectively.
|
204
|
+
# Returns nil if a block is provided.
|
205
|
+
sig { params(input: T.nilable(String), timeout_s: T.nilable(Numeric)).returns([String, String]) }
|
206
|
+
def communicate(input = nil, timeout_s = nil); end
|
207
|
+
|
208
|
+
# Does exactly what it says on the box.
|
209
|
+
#
|
210
|
+
# _@param_ `signal` — The signal to send to the child process. Accepts all the same arguments as Ruby's built-in {::Process::kill}, for instance a string like "INT" or "SIGINT", or a signal number like 2.
|
211
|
+
#
|
212
|
+
# _@return_ — See {::Process.kill}
|
213
|
+
#
|
214
|
+
# _@see_ `::Process.kill`
|
215
|
+
sig { params(signal: T.any(String, Symbol, Integer)).returns(Integer) }
|
216
|
+
def send_signal(signal); end
|
217
|
+
|
218
|
+
# Sends `SIGTERM` to the process.
|
219
|
+
#
|
220
|
+
# _@return_ — See {send_signal}
|
221
|
+
#
|
222
|
+
# _@see_ `send_signal`
|
223
|
+
sig { returns(Integer) }
|
224
|
+
def terminate; end
|
225
|
+
|
226
|
+
# Return a pair of values (child, mine), which are how the given file
|
227
|
+
# descriptor should appear to the child and to this process, respectively.
|
228
|
+
# "mine" is only non-nil in the case of a pipe (in fact, we just return a
|
229
|
+
# list of length one, since ruby will unpack nils from missing list items).
|
230
|
+
#
|
231
|
+
# _@param_ `fd`
|
232
|
+
#
|
233
|
+
# _@param_ `mode`
|
234
|
+
sig { params(fd: T.nilable(T.any(IO, Integer, String)), mode: String).returns(T::Array[IO]) }
|
235
|
+
def parse_fd(fd, mode); end
|
236
|
+
|
237
|
+
# The pair to parse_fd, returns whether or not the file descriptor was
|
238
|
+
# opened by us (and therefore should be closed by us).
|
239
|
+
#
|
240
|
+
# _@param_ `fd`
|
241
|
+
sig { params(fd: T.nilable(T.any(IO, Integer, String))).returns(T::Boolean) }
|
242
|
+
def our_fd?(fd); end
|
243
|
+
|
244
|
+
# Call IO.select timing out at Time `timeout_at`. If `timeout_at` is nil, never times out.
|
245
|
+
#
|
246
|
+
# _@param_ `read_array`
|
247
|
+
#
|
248
|
+
# _@param_ `write_array`
|
249
|
+
#
|
250
|
+
# _@param_ `err_array`
|
251
|
+
#
|
252
|
+
# _@param_ `timeout_at`
|
253
|
+
sig do
|
254
|
+
params(
|
255
|
+
read_array: T.nilable(T::Array[IO]),
|
256
|
+
write_array: T.nilable(T::Array[IO]),
|
257
|
+
err_array: T.nilable(T::Array[IO]),
|
258
|
+
timeout_at: T.nilable(T.any(Integer, Float))
|
259
|
+
).returns(T.nilable(T::Array[T::Array[IO]]))
|
260
|
+
end
|
261
|
+
def select_until(read_array, write_array, err_array, timeout_at); end
|
262
|
+
|
263
|
+
sig { void }
|
264
|
+
def self.handle_sigchld; end
|
265
|
+
|
266
|
+
# Wake up everyone. We can't tell who we should wake up without `wait`ing,
|
267
|
+
# and we want to let the process itself do that. In practice, we're not
|
268
|
+
# likely to have that many in-flight subprocesses, so this is probably not a
|
269
|
+
# big deal.
|
270
|
+
sig { void }
|
271
|
+
def self.wakeup_sigchld; end
|
272
|
+
|
273
|
+
# _@param_ `pid`
|
274
|
+
#
|
275
|
+
# _@param_ `fd`
|
276
|
+
sig { params(pid: Integer, fd: IO).void }
|
277
|
+
def self.register_pid(pid, fd); end
|
278
|
+
|
279
|
+
# _@param_ `pid`
|
280
|
+
sig { params(pid: Integer).void }
|
281
|
+
def self.unregister_pid(pid); end
|
282
|
+
|
283
|
+
# _@param_ `pid`
|
284
|
+
sig { params(pid: Integer).void }
|
285
|
+
def self.catching_sigchld(pid); end
|
286
|
+
|
287
|
+
# _@return_ — The `IO` that is connected to this process's `stdin`.
|
288
|
+
sig { returns(IO) }
|
289
|
+
attr_reader :stdin
|
290
|
+
|
291
|
+
# _@return_ — The `IO` that is connected to this process's `stdout`.
|
292
|
+
sig { returns(IO) }
|
293
|
+
attr_reader :stdout
|
294
|
+
|
295
|
+
# _@return_ — The `IO` that is connected to this process's `stderr`.
|
296
|
+
sig { returns(IO) }
|
297
|
+
attr_reader :stderr
|
298
|
+
|
299
|
+
# _@return_ — The command this process was invoked with.
|
300
|
+
sig { returns(T::Array[String]) }
|
301
|
+
attr_reader :command
|
302
|
+
|
303
|
+
# _@return_ — The process ID of the spawned process.
|
304
|
+
sig { returns(Integer) }
|
305
|
+
attr_reader :pid
|
306
|
+
|
307
|
+
# _@return_ — The exit status code of the process. Only
|
308
|
+
# set after the process has exited.
|
309
|
+
sig { returns(::Process::Status) }
|
310
|
+
attr_reader :status
|
311
|
+
end
|
312
|
+
end
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: subprocess
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
5
|
-
prerelease:
|
4
|
+
version: 1.5.4
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Carl Jackson
|
@@ -10,57 +9,65 @@ authors:
|
|
10
9
|
- Nelson Elhage
|
11
10
|
- Andy Brody
|
12
11
|
- Andreas Fuchs
|
13
|
-
autorequire:
|
12
|
+
autorequire:
|
14
13
|
bindir: bin
|
15
14
|
cert_chain: []
|
16
|
-
date:
|
15
|
+
date: 2021-01-13 00:00:00.000000000 Z
|
17
16
|
dependencies:
|
18
17
|
- !ruby/object:Gem::Dependency
|
19
18
|
name: minitest
|
20
19
|
requirement: !ruby/object:Gem::Requirement
|
21
|
-
none: false
|
22
20
|
requirements:
|
23
|
-
- - ~>
|
21
|
+
- - "~>"
|
24
22
|
- !ruby/object:Gem::Version
|
25
23
|
version: '5.0'
|
26
24
|
type: :development
|
27
25
|
prerelease: false
|
28
26
|
version_requirements: !ruby/object:Gem::Requirement
|
29
|
-
none: false
|
30
27
|
requirements:
|
31
|
-
- - ~>
|
28
|
+
- - "~>"
|
32
29
|
- !ruby/object:Gem::Version
|
33
30
|
version: '5.0'
|
34
31
|
- !ruby/object:Gem::Dependency
|
35
32
|
name: rake
|
36
33
|
requirement: !ruby/object:Gem::Requirement
|
37
|
-
none: false
|
38
34
|
requirements:
|
39
|
-
- -
|
35
|
+
- - ">="
|
40
36
|
- !ruby/object:Gem::Version
|
41
37
|
version: '0'
|
42
38
|
type: :development
|
43
39
|
prerelease: false
|
44
40
|
version_requirements: !ruby/object:Gem::Requirement
|
45
|
-
none: false
|
46
41
|
requirements:
|
47
|
-
- -
|
42
|
+
- - ">="
|
48
43
|
- !ruby/object:Gem::Version
|
49
44
|
version: '0'
|
50
45
|
- !ruby/object:Gem::Dependency
|
51
46
|
name: pry
|
52
47
|
requirement: !ruby/object:Gem::Requirement
|
53
|
-
none: false
|
54
48
|
requirements:
|
55
|
-
- -
|
49
|
+
- - ">="
|
56
50
|
- !ruby/object:Gem::Version
|
57
51
|
version: '0'
|
58
52
|
type: :development
|
59
53
|
prerelease: false
|
60
54
|
version_requirements: !ruby/object:Gem::Requirement
|
61
|
-
none: false
|
62
55
|
requirements:
|
63
|
-
- -
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: sord
|
61
|
+
requirement: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
64
71
|
- !ruby/object:Gem::Version
|
65
72
|
version: '0'
|
66
73
|
description: Control and communicate with spawned processes
|
@@ -74,33 +81,31 @@ executables: []
|
|
74
81
|
extensions: []
|
75
82
|
extra_rdoc_files: []
|
76
83
|
files:
|
77
|
-
- lib/subprocess/version.rb
|
78
|
-
- lib/subprocess.rb
|
79
84
|
- README.md
|
85
|
+
- lib/subprocess.rb
|
86
|
+
- lib/subprocess/version.rb
|
87
|
+
- rbi/subprocess.rbi
|
80
88
|
homepage: https://github.com/stripe/subprocess
|
81
89
|
licenses:
|
82
90
|
- MIT
|
83
|
-
|
91
|
+
metadata: {}
|
92
|
+
post_install_message:
|
84
93
|
rdoc_options: []
|
85
94
|
require_paths:
|
86
95
|
- lib
|
87
96
|
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
-
none: false
|
89
97
|
requirements:
|
90
|
-
- -
|
98
|
+
- - ">="
|
91
99
|
- !ruby/object:Gem::Version
|
92
100
|
version: '0'
|
93
101
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
-
none: false
|
95
102
|
requirements:
|
96
|
-
- -
|
103
|
+
- - ">="
|
97
104
|
- !ruby/object:Gem::Version
|
98
105
|
version: '0'
|
99
106
|
requirements: []
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
specification_version: 3
|
107
|
+
rubygems_version: 3.1.2
|
108
|
+
signing_key:
|
109
|
+
specification_version: 4
|
104
110
|
summary: A port of Python's subprocess module to Ruby
|
105
111
|
test_files: []
|
106
|
-
has_rdoc:
|