ruby-sh 2.2.6 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -3
- data/.ruby-version +1 -1
- data/Gemfile +6 -8
- data/Gemfile.lock +131 -75
- data/README.md +118 -152
- data/lib/rubsh/command.rb +5 -8
- data/lib/rubsh/running_command.rb +66 -108
- data/lib/rubsh/version.rb +1 -1
- data/lib/rubsh.rb +2 -6
- data/rubsh.gemspec +3 -3
- metadata +7 -14
- data/.overcommit.yml +0 -26
- data/lib/rubsh/running_pipeline.rb +0 -268
- data/lib/rubsh/shell/env.rb +0 -15
- data/lib/rubsh/shell.rb +0 -24
data/README.md
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
# Rubsh
|
1
|
+
# Rubsh (a.k.a. ruby-sh)
|
2
2
|
|
3
3
|
Rubsh (a.k.a. ruby-sh) - Inspired by [python-sh], allows you to call any program as if it were a function:
|
4
4
|
|
5
5
|
```ruby
|
6
6
|
require 'rubsh'
|
7
7
|
|
8
|
-
|
9
|
-
print(
|
8
|
+
ifconfig = Rubsh.cmd('ifconfig')
|
9
|
+
print(ifconfig.call('wlan0').stdout_data)
|
10
10
|
```
|
11
11
|
|
12
12
|
Output:
|
@@ -27,12 +27,11 @@ Note that these aren't Ruby functions, these are running the binary commands on
|
|
27
27
|
|
28
28
|
When using this library you can:
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
30
|
+
- Call any program as if it were a function.
|
31
|
+
- Get an exception when exit code is not 0.
|
32
|
+
- Force terminate the process if it does not finish within the timeout.
|
33
|
+
- Always split the shell command into tokens, reduce command injection risk.
|
34
|
+
- etc.
|
36
35
|
|
37
36
|
## Installation
|
38
37
|
|
@@ -44,50 +43,50 @@ gem 'ruby-sh', require: 'rubsh'
|
|
44
43
|
|
45
44
|
And then execute:
|
46
45
|
|
47
|
-
|
46
|
+
```console
|
47
|
+
$ bundle install
|
48
|
+
```
|
48
49
|
|
49
50
|
Or install it yourself as:
|
50
51
|
|
51
|
-
|
52
|
-
|
52
|
+
```console
|
53
|
+
$ gem install ruby-sh
|
54
|
+
```
|
53
55
|
|
54
56
|
## Usage
|
55
57
|
|
56
58
|
### Basic Syntax
|
57
59
|
|
58
60
|
```ruby
|
59
|
-
# Create a
|
60
|
-
|
61
|
-
|
62
|
-
# Create a command, use `command`/`cmd`
|
63
|
-
cmd = sh.cmd("ls")
|
61
|
+
# Create a command
|
62
|
+
ls = Rubsh.cmd("ls")
|
64
63
|
|
65
|
-
# Invoke
|
66
|
-
|
64
|
+
# Invoke it
|
65
|
+
r = ls.call("-la")
|
67
66
|
|
68
67
|
# Print result
|
69
|
-
print
|
68
|
+
print(r.stdout_data)
|
70
69
|
```
|
71
70
|
|
72
71
|
### Passing Arguments
|
73
72
|
|
74
73
|
```ruby
|
75
|
-
|
74
|
+
Rubsh.cmd("ls").call("-l", "/tmp", color: "always", human_readable: true)
|
76
75
|
# => ["/usr/bin/ls", "-l", "/tmp", "--color=always", "--human-readable"]
|
77
76
|
|
78
|
-
|
77
|
+
Rubsh.cmd("curl").call("https://www.ruby-lang.org/", o: "page.html", silent: true)
|
79
78
|
# => ["/usr/bin/curl", "https://www.ruby-lang.org/", "-opage.html", "--silent"]
|
80
79
|
|
81
|
-
|
80
|
+
Rubsh.cmd("git").call(:status, { v: true })
|
82
81
|
# => ["/usr/bin/git", "status", "-v"]
|
83
82
|
|
84
|
-
|
83
|
+
Rubsh.cmd("git").call(:status, { v: true }, "--", ".")
|
85
84
|
# => ["/usr/bin/git", "status", "-v", "--", "."]
|
86
85
|
|
87
|
-
|
86
|
+
Rubsh.cmd("git").call(:status, { v: proc{ true }, short: true }, "--", ".")
|
88
87
|
# => ["/usr/bin/git", "status", "-v", "--short", "--", "."]
|
89
88
|
|
90
|
-
|
89
|
+
Rubsh.cmd("git").call(:status, { v: true }, v: false)
|
91
90
|
# => ["/usr/bin/git", "status"]
|
92
91
|
```
|
93
92
|
|
@@ -95,19 +94,19 @@ sh.cmd("git").call(:status, { v: true }, v: false)
|
|
95
94
|
|
96
95
|
```ruby
|
97
96
|
# Successful
|
98
|
-
r =
|
97
|
+
r = Rubsh.cmd("ls").call("/")
|
99
98
|
r.exit_code # => 0
|
100
99
|
|
101
100
|
# a `CommandReturnFailureError` raised when run failure
|
102
101
|
begin
|
103
|
-
|
102
|
+
Rubsh.cmd("ls").call("/some/non-existant/folder")
|
104
103
|
rescue Rubsh::Exceptions::CommandReturnFailureError => e
|
105
104
|
e.exit_code # => 2
|
106
105
|
end
|
107
106
|
|
108
107
|
# Treats as success use `:_ok_code`
|
109
|
-
r =
|
110
|
-
r =
|
108
|
+
r = Rubsh.cmd("ls").call("/some/non-existant/folder", _ok_code: [0, 1, 2])
|
109
|
+
r = Rubsh.cmd("ls").call("/some/non-existant/folder", _ok_code: 0..2)
|
111
110
|
r.exit_code # => 2
|
112
111
|
```
|
113
112
|
|
@@ -115,29 +114,29 @@ r.exit_code # => 2
|
|
115
114
|
|
116
115
|
```ruby
|
117
116
|
# Filename
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
117
|
+
Rubsh.cmd("ls").call(_out: "/tmp/dir_content")
|
118
|
+
Rubsh.cmd("ls").call(_out: ["/tmp/dir_content", "w"])
|
119
|
+
Rubsh.cmd("ls").call(_out: ["/tmp/dir_content", "w", 0600])
|
120
|
+
Rubsh.cmd("ls").call(_out: ["/tmp/dir_content", File::WRONLY|File::EXCL|File::CREAT, 0600])
|
122
121
|
|
123
122
|
# File object
|
124
|
-
File.open("/tmp/dir_content", "w") { |f|
|
123
|
+
File.open("/tmp/dir_content", "w") { |f| Rubsh.cmd("ls").call(_out: f) }
|
125
124
|
|
126
125
|
# `stdout_data` & `stderr_data`
|
127
|
-
r =
|
126
|
+
r = Rubsh.cmd("sh").call("-c", "echo out; echo err >&2")
|
128
127
|
r.stdout_data # => "out\n"
|
129
128
|
r.stderr_data # => "err\n"
|
130
129
|
|
131
130
|
# Redirects stderr and stderr to the same place use `_err_to_out`
|
132
|
-
r =
|
131
|
+
r = Rubsh.cmd("sh").call("-c", "echo out; echo err >&2", _err_to_out: true)
|
133
132
|
r.stdout_data # => "out\nerr\n"
|
134
133
|
r.stderr_data # => nil
|
135
134
|
|
136
135
|
# Read input from data
|
137
|
-
|
136
|
+
Rubsh.cmd("cat").call(_in_data: "hello").stdout_data # => "hello"
|
138
137
|
|
139
138
|
# Read input from file
|
140
|
-
|
139
|
+
Rubsh.cmd("cat").call(_in: "/some/existant/file")
|
141
140
|
```
|
142
141
|
|
143
142
|
### Incremental Iteration
|
@@ -146,9 +145,9 @@ sh.cmd("cat").call_with(_in: "/some/existant/file")
|
|
146
145
|
# By default, output is line-buffered, so the body of the loop will only run
|
147
146
|
# when your process produces a newline. You can change this by changing the
|
148
147
|
# buffer size of the command’s output with `_out_bufsize`/`_err_bufsize`.
|
149
|
-
tail =
|
150
|
-
tail.
|
151
|
-
print
|
148
|
+
tail = Rubsh.cmd("tail")
|
149
|
+
tail.call("-f", "/var/log/some_log_file.log", _capture: ->(stdout, _stderr) {
|
150
|
+
print(stdout)
|
152
151
|
})
|
153
152
|
```
|
154
153
|
|
@@ -156,43 +155,43 @@ tail.call_with("-f", "/var/log/some_log_file.log", _capture: ->(stdout, _stderr)
|
|
156
155
|
|
157
156
|
```ruby
|
158
157
|
# Blocks
|
159
|
-
|
160
|
-
|
158
|
+
Rubsh.cmd("sleep").call(3)
|
159
|
+
print("...3 seconds later")
|
161
160
|
|
162
161
|
# Doesn't block
|
163
|
-
r =
|
164
|
-
|
162
|
+
r = Rubsh.cmd("sleep").call(3, _bg: true)
|
163
|
+
print("prints immediately!")
|
165
164
|
r.wait()
|
166
|
-
|
165
|
+
print("...and 3 seconds later")
|
167
166
|
|
168
167
|
# Timeout
|
169
|
-
r =
|
170
|
-
|
168
|
+
r = Rubsh.cmd("sleep").call(30, _bg: true)
|
169
|
+
print("prints immediately!")
|
171
170
|
r.wait(timeout: 3)
|
172
|
-
|
171
|
+
print("...and 3 seconds later")
|
173
172
|
```
|
174
173
|
|
175
174
|
### Baking
|
176
175
|
|
177
176
|
```ruby
|
178
|
-
ll =
|
179
|
-
ll.
|
177
|
+
ll = Rubsh.cmd("ls").bake("-l")
|
178
|
+
ll.call("/tmp") # => ["/usr/bin/ls", "-l", "/tmp"]
|
180
179
|
|
181
180
|
# Equivalent
|
182
|
-
|
181
|
+
Rubsh.cmd("ls").call("-l", "/tmp")
|
183
182
|
|
184
183
|
# Calling whoami on a server. this is a lot to type out, especially if you wanted
|
185
184
|
# to call many commands (not just whoami) back to back on the same server resolves
|
186
185
|
# to "/usr/bin/ssh myserver.com -p 1393 whoami"
|
187
|
-
|
186
|
+
Rubsh.cmd('ssh').call("myserver.com", "-p 1393", "whoami")
|
188
187
|
|
189
188
|
# Wouldn't it be nice to bake the common parameters into the ssh command?
|
190
|
-
myserver =
|
191
|
-
myserver.
|
192
|
-
myserver.
|
189
|
+
myserver = Rubsh.cmd('ssh').bake("myserver.com", p: 1393)
|
190
|
+
myserver.call('whoami')
|
191
|
+
myserver.call('pwd')
|
193
192
|
|
194
193
|
# With a special kwarg
|
195
|
-
sleep =
|
194
|
+
sleep = Rubsh.cmd('sleep').bake(_timeout: 2)
|
196
195
|
sleep.call(1) # => ok
|
197
196
|
sleep.call(3) # => a `CommandReturnFailureError` raised
|
198
197
|
```
|
@@ -201,83 +200,67 @@ sleep.call(3) # => a `CommandReturnFailureError` raised
|
|
201
200
|
|
202
201
|
```ruby
|
203
202
|
# Use `bake`
|
204
|
-
gst =
|
203
|
+
gst = Rubsh.cmd("git").bake("status")
|
205
204
|
|
206
|
-
gst.
|
207
|
-
gst.
|
205
|
+
gst.call() # => ["/usr/bin/git", "status"]
|
206
|
+
gst.call("-s") # => ["/usr/bin/git", "status", "-s"]
|
208
207
|
```
|
209
208
|
|
210
|
-
### Piping
|
211
|
-
|
212
|
-
```ruby
|
213
|
-
# Run a series of commands connected by `_pipeline`
|
214
|
-
r = sh.pipeline(_in_data: "hello world") do |pipeline|
|
215
|
-
sh.cmd("cat").call_with(_pipeline: pipeline)
|
216
|
-
sh.cmd("wc").call_with("-c", _pipeline: pipeline)
|
217
|
-
end
|
218
|
-
r.stdout_data # => "11\n"
|
219
|
-
```
|
220
|
-
|
221
|
-
|
222
209
|
## Reference
|
223
210
|
|
224
211
|
### Special Kwargs
|
225
212
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
* `_pipeline`:
|
278
|
-
* use: Specifies the :pipeline.
|
279
|
-
* default value: `nil`
|
280
|
-
|
213
|
+
- `_in_data`:
|
214
|
+
- use: Specifies an argument for the process to use as its standard input data.
|
215
|
+
- default value: `nil`
|
216
|
+
- `_in`:
|
217
|
+
- use: Specifies an argument for the process to use as its standard input.
|
218
|
+
- default value: `nil`
|
219
|
+
- `_out`:
|
220
|
+
- use: Where to redirect STDOUT to.
|
221
|
+
- default value: `nil`
|
222
|
+
- `_err`:
|
223
|
+
- use: Where to redirect STDERR to.
|
224
|
+
- default value: `nil`
|
225
|
+
- `_err_to_out`:
|
226
|
+
- use: If true, duplicate the file descriptor bound to the process’s STDOUT also to STDERR.
|
227
|
+
- default value: `false`
|
228
|
+
- `_capture`:
|
229
|
+
- use: Iterates over STDOUT/STDERR.
|
230
|
+
- default value: `nil`
|
231
|
+
- `_bg`:
|
232
|
+
- use: Runs a command in the background. The command will return immediately, and you will have to run RunningCommand#wait on it to ensure it terminates.
|
233
|
+
- default value: `false`
|
234
|
+
- `_timeout`:
|
235
|
+
- use: How much time, in seconds, we should give the process to complete. If the process does not finish within the timeout, it will be terminated.
|
236
|
+
- default value: `nil`
|
237
|
+
- `_env`:
|
238
|
+
- use: A dictionary defining the only environment variables that will be made accessible to the process. If not specified, the calling process’s environment variables are used.
|
239
|
+
- default value: `nil`
|
240
|
+
- `_cwd`:
|
241
|
+
- use: Current working directory of the process.
|
242
|
+
- default value: `nil`
|
243
|
+
- `_ok_code`:
|
244
|
+
- use: Some misbehaved programs use exit codes other than 0 to indicate success. Set to treats as success.
|
245
|
+
- default value: `[0]`
|
246
|
+
- `_no_out`:
|
247
|
+
- use: Disables STDOUT being internally stored. This is useful for commands that produce huge amounts of output that you don’t need, that would otherwise be hogging memory if stored internally by Rubsh.
|
248
|
+
- default value: `false`
|
249
|
+
- `_no_err`:
|
250
|
+
- use: Disables STDERR being internally stored. This is useful for commands that produce huge amounts of output that you don’t need, that would otherwise be hogging memory if stored internally by Rubsh.
|
251
|
+
- default value: `false`
|
252
|
+
- `_out_bufsize`:
|
253
|
+
- use: The STDOUT buffer size. nil for unbuffered, 0 for line buffered, anything else for a buffer of that amount.
|
254
|
+
- default value: `0`
|
255
|
+
- `_err_bufsize`:
|
256
|
+
- use: The STDERR buffer size. nil for unbuffered, 0 for line buffered, anything else for a buffer of that amount.
|
257
|
+
- default value: `0`
|
258
|
+
- `_long_sep`:
|
259
|
+
- use: This is the character(s) that separate a program’s long argument’s key from the value.
|
260
|
+
- default value: `"="`
|
261
|
+
- `_long_prefix`:
|
262
|
+
- use: This is the character(s) that prefix a long argument for the program being run. Some programs use single dashes, for example, and do not understand double dashes.
|
263
|
+
- default value: `"--"`
|
281
264
|
|
282
265
|
## FAQ
|
283
266
|
|
@@ -288,9 +271,8 @@ Glob expansion is a feature of a shell, like Bash, and is performed by the shell
|
|
288
271
|
### How do I execute a bash builtin?
|
289
272
|
|
290
273
|
```ruby
|
291
|
-
|
292
|
-
rawsh
|
293
|
-
print(rawsh.call_with('echo Hello').stdout_data) # => "Hello\n"
|
274
|
+
rawsh = Rubsh.cmd('bash').bake('-c')
|
275
|
+
print(rawsh.call('echo Hello').stdout_data) # => "Hello\n"
|
294
276
|
```
|
295
277
|
|
296
278
|
### How do I call a program that isn’t in $PATH?
|
@@ -298,16 +280,7 @@ print(rawsh.call_with('echo Hello').stdout_data) # => "Hello\n"
|
|
298
280
|
Use absolute binpath
|
299
281
|
|
300
282
|
```ruby
|
301
|
-
|
302
|
-
sh.cmd('/path/to/command').call()
|
303
|
-
```
|
304
|
-
|
305
|
-
Or use `Rubsh::Shell::Env#path`
|
306
|
-
|
307
|
-
```ruby
|
308
|
-
sh = Rubsh.new
|
309
|
-
sh.env.path << "/dir/to/command/"
|
310
|
-
sh.cmd('command').call()
|
283
|
+
Rubsh.cmd('/path/to/command').call()
|
311
284
|
```
|
312
285
|
|
313
286
|
### How do I run a command and connect it to stdout and stdin?
|
@@ -325,36 +298,29 @@ my-command --arg1=val1 arg2 --arg3=val3
|
|
325
298
|
Use:
|
326
299
|
|
327
300
|
```ruby
|
328
|
-
|
329
|
-
sh.cmd('my-command').call_with({ arg1: "val1" }, "args2", { arg3: "val3" })
|
301
|
+
Rubsh.cmd('my-command').call({ arg1: "val1" }, "args2", { arg3: "val3" })
|
330
302
|
```
|
331
303
|
|
332
|
-
|
333
304
|
## Development
|
334
305
|
|
335
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `
|
306
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
336
307
|
|
337
308
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
338
309
|
|
339
|
-
|
340
310
|
## Contributing
|
341
311
|
|
342
312
|
Bug reports and pull requests are welcome on GitHub at https://github.com/souk4711/rubsh. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/souk4711/rubsh/blob/main/CODE_OF_CONDUCT.md).
|
343
313
|
|
344
|
-
|
345
314
|
## Acknowledgements
|
346
315
|
|
347
|
-
|
348
|
-
|
316
|
+
- Special thanks to [python-sh].
|
349
317
|
|
350
318
|
## License
|
351
319
|
|
352
320
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
353
321
|
|
354
|
-
|
355
322
|
## Code of Conduct
|
356
323
|
|
357
324
|
Everyone interacting in the Rubsh project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/souk4711/rubsh/blob/main/CODE_OF_CONDUCT.md).
|
358
325
|
|
359
|
-
|
360
|
-
[python-sh]:https://github.com/amoffat/sh
|
326
|
+
[python-sh]: https://github.com/amoffat/sh
|
data/lib/rubsh/command.rb
CHANGED
@@ -6,8 +6,7 @@ module Rubsh
|
|
6
6
|
# When a Command object is called, the result that is returned is a RunningCommand
|
7
7
|
# object, which represents the Command put into an execution state.
|
8
8
|
class Command
|
9
|
-
def initialize(
|
10
|
-
@sh = sh
|
9
|
+
def initialize(prog)
|
11
10
|
@prog = prog.to_s
|
12
11
|
@progpath = resolve_progpath(@prog)
|
13
12
|
@baked_opts = []
|
@@ -18,8 +17,7 @@ module Rubsh
|
|
18
17
|
# @return [RunningCommand] An new instance of RunningCommand with execution state.
|
19
18
|
# @example
|
20
19
|
#
|
21
|
-
#
|
22
|
-
# git = Rubsh::Command.new(sh, "git")
|
20
|
+
# git = Rubsh::Command.new("git")
|
23
21
|
# git.call() # => ["git"]
|
24
22
|
# git.call("") # => ["git", ""]
|
25
23
|
# git.call("status") # => ["git", "status"]
|
@@ -30,17 +28,16 @@ module Rubsh
|
|
30
28
|
# git.call(:status, { v: proc{ true }, short: true }, "--", ".") # => ["git", "status", "-v", "--short=true", "--", "."]
|
31
29
|
# git.call(:status, { untracked_files: "normal" }, "--", ".") # => ["git", "status", "--untracked-files=normal", "--", "."]
|
32
30
|
def call(*args, **kwargs)
|
33
|
-
rcmd = RunningCommand.new(@
|
31
|
+
rcmd = RunningCommand.new(@prog, @progpath, *@baked_opts, *args, **kwargs)
|
34
32
|
rcmd.__run
|
35
33
|
rcmd
|
36
34
|
end
|
37
|
-
alias_method :call_with, :call
|
38
35
|
|
39
36
|
# @param args [String, Symbol, #to_s, Hash]
|
40
37
|
# @param kwargs [Hash]
|
41
38
|
# @return [Command] a new instance of Command with baked options.
|
42
39
|
def bake(*args, **kwargs)
|
43
|
-
cmd = Command.new(@
|
40
|
+
cmd = Command.new(@prog)
|
44
41
|
cmd.__bake!(*@baked_opts, *args, **kwargs)
|
45
42
|
cmd
|
46
43
|
end
|
@@ -70,7 +67,7 @@ module Rubsh
|
|
70
67
|
progpath = prog
|
71
68
|
end
|
72
69
|
else
|
73
|
-
|
70
|
+
::ENV["PATH"].split(::File::PATH_SEPARATOR).each do |path|
|
74
71
|
filepath = ::File.join(path, prog)
|
75
72
|
if ::File.executable?(filepath) && ::File.file?(filepath)
|
76
73
|
progpath = filepath
|