subprocess 1.1.0 → 1.5.4
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Build Status](https://travis-ci.org/stripe/subprocess.svg?branch=master)](https://travis-ci.org/stripe/subprocess) [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/github/stripe/subprocess/Subprocess)
|
3
2
|
|
4
3
|
![Jacques Cousteau Submarine](http://i.imgur.com/lmej24F.jpg)
|
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:
|