shellex 1.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.
- data/.gitignore +18 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +102 -0
- data/Rakefile +9 -0
- data/lib/lib.iml +12 -0
- data/lib/shellex.rb +144 -0
- data/lib/shellex/version.rb +3 -0
- data/shellex.gemspec +23 -0
- data/test/shellex_test.rb +142 -0
- metadata +103 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Dima Sabanin
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# Shellex [](http://travis-ci.org/dsabanin/shellex)
|
2
|
+
|
3
|
+
Shellex allows you to run shell commands from your ruby scripts in a more robust and secure way than the built-in options.
|
4
|
+
We had a security audit in http://beanstalkapp.com recently which showed many problems caused by shell injections. This code
|
5
|
+
is the result of our attempt to fix these issues once and for all.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'shellex'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install shellex
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Grabbing STDOUT output:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
stdout, stderr = shellex("echo hello, world!")
|
27
|
+
# stdout => "hello, world!\n"
|
28
|
+
# stderr => ""
|
29
|
+
```
|
30
|
+
|
31
|
+
Grabbing STDERR output:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
stdout, stderr = shellex("echo error here 1>&2")
|
35
|
+
# stdout => ""
|
36
|
+
# stderr => "error here\n"
|
37
|
+
```
|
38
|
+
|
39
|
+
Convenience methods:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
shellex("echo hello, world").to_s # => "hello, world\n"
|
43
|
+
shellex("echo hello, world").stdout # => "hello, world\n"
|
44
|
+
shellex("echo error here 1>&2").stderr # => "error here\n"
|
45
|
+
```
|
46
|
+
|
47
|
+
Providing STDIN input:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
shellex("cat -", :input => "hello").stdout # => "hello"
|
51
|
+
```
|
52
|
+
|
53
|
+
By default if you don't provide input we close the STDIN stream, but if you want you can leave it open:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
shellex("cat /dev/stdin", :close_stdin => false)
|
57
|
+
```
|
58
|
+
|
59
|
+
Timeouts (default timeout is set to 5 minutes):
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
shellex("sleep 10", :timeout => 1) # raises ShellExecutionTimeout exception
|
63
|
+
```
|
64
|
+
|
65
|
+
Interpolation of arguments:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
# to_s gets called on all arguments
|
69
|
+
shellex("echo ? ? ?", 1, "blah", :symbol)
|
70
|
+
# executes: echo '1' 'blah' 'symbol'
|
71
|
+
|
72
|
+
# ?& interpolates each array element separately
|
73
|
+
shellex("? ?& ?", "echo", [1,2,3,4], "abc")
|
74
|
+
# executes: 'echo' '1' '2' '3' '4' 'abc'
|
75
|
+
|
76
|
+
# ?& requires array to be present in the respective position
|
77
|
+
shellex("? ?& ?", "echo", 1) # raises ShellArgumentMissing
|
78
|
+
|
79
|
+
# ?~ escapes question mark
|
80
|
+
shellex("? ?~", "echo", "ello")
|
81
|
+
# executes: 'echo' ?
|
82
|
+
|
83
|
+
# ? by default will turn nil into empty string
|
84
|
+
shellex("echo ?", nil)
|
85
|
+
# executes: echo ''
|
86
|
+
|
87
|
+
# ?? will skip the argument if it's nil
|
88
|
+
shellex("echo ??", nil)
|
89
|
+
# executes: echo
|
90
|
+
|
91
|
+
# Shell injection protection
|
92
|
+
shellex("echo ?", "'; rm -Rf /; '")
|
93
|
+
# executes harmless: echo ''\''; rm -Rf /; '\'''
|
94
|
+
```
|
95
|
+
|
96
|
+
## Contributing
|
97
|
+
|
98
|
+
1. Fork it
|
99
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
100
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
101
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
102
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/lib/lib.iml
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<module type="RUBY_MODULE" version="4">
|
3
|
+
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
4
|
+
<exclude-output />
|
5
|
+
<content url="file://$MODULE_DIR$">
|
6
|
+
<sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
|
7
|
+
</content>
|
8
|
+
<orderEntry type="inheritedJdk" />
|
9
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
10
|
+
</component>
|
11
|
+
</module>
|
12
|
+
|
data/lib/shellex.rb
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
require "shellex/version"
|
2
|
+
require 'popen4'
|
3
|
+
|
4
|
+
class ShellExecutionError < StandardError; end
|
5
|
+
class ShellExecutionFailed < ShellExecutionError
|
6
|
+
attr_accessor :output_without_command
|
7
|
+
end
|
8
|
+
class ShellExecutionTimeout < ShellExecutionError; end
|
9
|
+
class ShellArgumentMissing < ShellExecutionError; end
|
10
|
+
|
11
|
+
module Kernel
|
12
|
+
SHELLEX_IO_BUFFER = 32*1024
|
13
|
+
DEFAULT_TIMEOUT = 5*60
|
14
|
+
|
15
|
+
def _shellex_retvalue(ret)
|
16
|
+
def ret.stdout; self[0] end
|
17
|
+
def ret.stderr; self[1] end
|
18
|
+
def ret.to_s; stdout end
|
19
|
+
def ret.to_str; stdout end
|
20
|
+
ret.freeze
|
21
|
+
end
|
22
|
+
|
23
|
+
def _shellex_extend_status(status)
|
24
|
+
def status.command=(cmd)
|
25
|
+
@command = cmd
|
26
|
+
end
|
27
|
+
def status.command
|
28
|
+
@command
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def shellex(cmd, *args)
|
33
|
+
stdout, stderr, status = silent_shellex(cmd, *args)
|
34
|
+
if status.success?
|
35
|
+
ret = [stdout, stderr]
|
36
|
+
return _shellex_retvalue(ret)
|
37
|
+
else
|
38
|
+
exc = ShellExecutionFailed.new("#{status.command} (exit code: #{status.exitstatus})\nOutput: #{stderr}")
|
39
|
+
exc.output_without_command = stderr
|
40
|
+
raise exc
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def silent_shellex(cmd, *args)
|
45
|
+
opts = {:timeout => DEFAULT_TIMEOUT, :close_stdin => true}
|
46
|
+
if args.last.is_a?(Hash)
|
47
|
+
opts = opts.merge(args.pop)
|
48
|
+
end
|
49
|
+
|
50
|
+
cmd = cmd.with_args(*args)
|
51
|
+
out, err, pid, status = '', '', nil, nil
|
52
|
+
|
53
|
+
begin
|
54
|
+
Timeout.timeout(opts[:timeout].to_i) do
|
55
|
+
status = Open4.open4(cmd) do |pid, stdin, stdout, stderr|
|
56
|
+
stdin.write(opts[:input]) if opts[:input]
|
57
|
+
stdin.close if opts[:close_stdin]
|
58
|
+
|
59
|
+
while tmp = stdout.read(SHELLEX_IO_BUFFER)
|
60
|
+
out << tmp
|
61
|
+
end
|
62
|
+
|
63
|
+
while tmp = stderr.read(SHELLEX_IO_BUFFER)
|
64
|
+
err << tmp
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
rescue Exception => e
|
69
|
+
if pid
|
70
|
+
Process.kill 9, pid
|
71
|
+
Process.wait pid
|
72
|
+
end
|
73
|
+
|
74
|
+
# Exceptions coming from popen4 are deserialized from forked process, so they are not caught by listing
|
75
|
+
# class name in rescue clause
|
76
|
+
case e.class.to_s
|
77
|
+
when /Timeout::Error/
|
78
|
+
raise ShellExecutionTimeout, "Timeout after #{opts[:timeout]} secs while running '#{cmd}'"
|
79
|
+
else
|
80
|
+
raise ShellExecutionError, e.message
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
_shellex_debug(cmd, err, opts, out)
|
85
|
+
|
86
|
+
_shellex_extend_status(status)
|
87
|
+
status.command = cmd
|
88
|
+
|
89
|
+
return out, err, status
|
90
|
+
end
|
91
|
+
|
92
|
+
def _shellex_debug(cmd, err, opts, out)
|
93
|
+
if defined?(Rails) and Rails.env.development?
|
94
|
+
Rails.logger.info { "Executed: #{cmd}\n STDIN: #{opts[:input].inspect} STDOUT: #{out.inspect}\nSTDERR: #{err.inspect}" }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class String
|
100
|
+
def with_args(*args)
|
101
|
+
escape = proc { |val| val.to_s._shellex_escape }
|
102
|
+
ignore_nil = proc { |val| escape.call(val) unless val.nil? }
|
103
|
+
|
104
|
+
gsub(/(\?\&|\?\?|\?\!|\?~|\?)/) do |match|
|
105
|
+
val = args.shift
|
106
|
+
|
107
|
+
case match
|
108
|
+
when "?~" # Escape question mark
|
109
|
+
"?"
|
110
|
+
when "?!" # Argument can't be nil
|
111
|
+
if val.nil?
|
112
|
+
raise ShellArgumentMissing, "Argument marked as required with ?! is nil"
|
113
|
+
else
|
114
|
+
escape.call(val)
|
115
|
+
end
|
116
|
+
when "??" # Argument will be omitted if nil
|
117
|
+
ignore_nil.call(val)
|
118
|
+
when "?" # Argument will be escaped even if nil
|
119
|
+
escape.call(val)
|
120
|
+
when "?&" # Argument has to be array and each element will be escaped or ommitted if nil
|
121
|
+
if val.is_a?(Array)
|
122
|
+
val.map(&ignore_nil).compact.join(" ")
|
123
|
+
else
|
124
|
+
raise ShellArgumentMissing, "If ?& is present in this position, #{val.inspect} should be an Array"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end.strip
|
128
|
+
end
|
129
|
+
|
130
|
+
def _shellex_escape
|
131
|
+
if self.empty? or self.strip == ""
|
132
|
+
return "''"
|
133
|
+
end
|
134
|
+
self.split(/'/, -1).map{|e| "'#{e}'"}.join("\\'")
|
135
|
+
end
|
136
|
+
|
137
|
+
def shellex(*args)
|
138
|
+
shellex(self, *args)
|
139
|
+
end
|
140
|
+
|
141
|
+
def silent_shellex(*args)
|
142
|
+
silent_shellex(self, *args)
|
143
|
+
end
|
144
|
+
end
|
data/shellex.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'shellex/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "shellex"
|
8
|
+
spec.version = Shellex::VERSION
|
9
|
+
spec.authors = ["Dima Sabanin"]
|
10
|
+
spec.email = ["sdmitry@gmail.com"]
|
11
|
+
spec.description = %q{Shell execution made easy and secure}
|
12
|
+
spec.summary = %q{Allows you to securely execute shell code with exceptions on errors and timeouts}
|
13
|
+
spec.homepage = "https://github.com/dsabanin/shellex"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'shellex'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'shoulda/context'
|
4
|
+
|
5
|
+
class ShellexTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
context "ShellEx" do
|
8
|
+
|
9
|
+
context "capturing" do
|
10
|
+
should "stdout capturing" do
|
11
|
+
stdout, stderr = shellex("echo stdout")
|
12
|
+
assert_equal "stdout\n", stdout
|
13
|
+
assert_equal "", stderr
|
14
|
+
end
|
15
|
+
|
16
|
+
should "stderr capturing" do
|
17
|
+
stdout, stderr = shellex("echo stderr 1>&2")
|
18
|
+
assert_equal "", stdout
|
19
|
+
assert_equal "stderr\n", stderr
|
20
|
+
end
|
21
|
+
|
22
|
+
should "stdout and stderr capturing" do
|
23
|
+
stdout, stderr = shellex("echo stdout; echo stderr 1>&2")
|
24
|
+
assert_equal "stdout\n", stdout
|
25
|
+
assert_equal "stderr\n", stderr
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
should "eat stdin input" do
|
30
|
+
stdout, stderr = shellex("cat -", :input => "hello")
|
31
|
+
assert_equal "hello", stdout
|
32
|
+
end
|
33
|
+
|
34
|
+
should "timeout" do
|
35
|
+
assert_raises(ShellExecutionTimeout) do
|
36
|
+
shellex("sleep 10", :timeout => 1)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
should "timeout on blocking io" do
|
41
|
+
assert_raises(ShellExecutionTimeout) do
|
42
|
+
shellex("cat /dev/stdin", :timeout => 1, :close_stdin => false)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "interpolation" do
|
47
|
+
should "interpolate and escape the args" do
|
48
|
+
real = "echo ? ? ?".with_args(1, "blah", :symbol)
|
49
|
+
assert_equal "echo '1' 'blah' 'symbol'", real
|
50
|
+
end
|
51
|
+
|
52
|
+
should "interpolate ?& as series of args" do
|
53
|
+
real = "? ?&".with_args("echo", [1,2,3,4])
|
54
|
+
assert_equal "'echo' '1' '2' '3' '4'", real
|
55
|
+
end
|
56
|
+
|
57
|
+
should "interpolate ?& as series of args in the middle" do
|
58
|
+
real = "? ?& ?".with_args("echo", [1,2,3,4], "abc")
|
59
|
+
assert_equal "'echo' '1' '2' '3' '4' 'abc'", real
|
60
|
+
end
|
61
|
+
|
62
|
+
should "raise error if array for ?& is not given" do
|
63
|
+
assert_raises(ShellArgumentMissing) do
|
64
|
+
"? ?& ?".with_args("echo", 1)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
should "interpolate empty array to empty space" do
|
69
|
+
real = "? ?& ?".with_args("echo", [], "abc")
|
70
|
+
assert_equal "'echo' 'abc'", real
|
71
|
+
end
|
72
|
+
|
73
|
+
should "interpolate nil in array to empty space" do
|
74
|
+
real = "? ?& ?".with_args("echo", [1, nil, 3], "abc")
|
75
|
+
assert_equal "'echo' '1' '3' 'abc'", real
|
76
|
+
end
|
77
|
+
|
78
|
+
should "interpolate on shellex call" do
|
79
|
+
stdout, stderr = shellex("? ?", "echo", "stdout")
|
80
|
+
assert_equal "stdout\n", stdout
|
81
|
+
end
|
82
|
+
|
83
|
+
should "interpolate nil value as empty string with ?" do
|
84
|
+
real = "? ?".with_args("echo", nil)
|
85
|
+
assert_equal "'echo' ''", real
|
86
|
+
|
87
|
+
real = "? ?".with_args("echo", "blah")
|
88
|
+
assert_equal "'echo' 'blah'", real
|
89
|
+
end
|
90
|
+
|
91
|
+
should "ignore nil values with ??" do
|
92
|
+
real = "? ??".with_args("echo", nil)
|
93
|
+
assert_equal "'echo'", real
|
94
|
+
|
95
|
+
real = "? ??".with_args("echo", "blah")
|
96
|
+
assert_equal "'echo' 'blah'", real
|
97
|
+
end
|
98
|
+
|
99
|
+
should "raise on required arguments" do
|
100
|
+
assert_raises(ShellArgumentMissing) do
|
101
|
+
real = "? ?!".with_args("echo", nil)
|
102
|
+
end
|
103
|
+
|
104
|
+
real = "? ??".with_args("echo", "blah")
|
105
|
+
assert_equal "'echo' 'blah'", real
|
106
|
+
end
|
107
|
+
|
108
|
+
should "be able to escape the question mark" do
|
109
|
+
real = "? ?~".with_args("echo", "ello")
|
110
|
+
assert_equal "'echo' ?", real
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "error handling" do
|
115
|
+
should "raise error if exit value is not zero" do
|
116
|
+
assert_raises(ShellExecutionFailed) do
|
117
|
+
shellex("test a = b")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
should "have silent version that eats errors" do
|
122
|
+
cmd = "echo ? 1>&2; test a = ?"
|
123
|
+
stdout, stderr, status = silent_shellex(cmd, "stderr", "b")
|
124
|
+
assert_equal "", stdout
|
125
|
+
assert stderr.include?("stderr"), "Got: #{stderr}"
|
126
|
+
assert_equal false, status.success?
|
127
|
+
assert_equal "echo 'stderr' 1>&2; test a = 'b'", status.command
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context "should have singleton-array api" do
|
132
|
+
should "provide stdout/stderr methods" do
|
133
|
+
api = shellex("echo test")
|
134
|
+
assert_equal "test\n", api.stdout
|
135
|
+
assert_equal "test\n", api.to_s
|
136
|
+
assert_equal "test\n", api.to_str
|
137
|
+
assert_equal "", api.stderr
|
138
|
+
assert api.frozen?
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shellex
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Dima Sabanin
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2013-03-15 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: bundler
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 9
|
29
|
+
segments:
|
30
|
+
- 1
|
31
|
+
- 3
|
32
|
+
version: "1.3"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rake
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id002
|
49
|
+
description: Shell execution made easy and secure
|
50
|
+
email:
|
51
|
+
- sdmitry@gmail.com
|
52
|
+
executables: []
|
53
|
+
|
54
|
+
extensions: []
|
55
|
+
|
56
|
+
extra_rdoc_files: []
|
57
|
+
|
58
|
+
files:
|
59
|
+
- .gitignore
|
60
|
+
- Gemfile
|
61
|
+
- LICENSE.txt
|
62
|
+
- README.md
|
63
|
+
- Rakefile
|
64
|
+
- lib/lib.iml
|
65
|
+
- lib/shellex.rb
|
66
|
+
- lib/shellex/version.rb
|
67
|
+
- shellex.gemspec
|
68
|
+
- test/shellex_test.rb
|
69
|
+
homepage: https://github.com/dsabanin/shellex
|
70
|
+
licenses:
|
71
|
+
- MIT
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options: []
|
74
|
+
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
hash: 3
|
83
|
+
segments:
|
84
|
+
- 0
|
85
|
+
version: "0"
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
hash: 3
|
92
|
+
segments:
|
93
|
+
- 0
|
94
|
+
version: "0"
|
95
|
+
requirements: []
|
96
|
+
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 1.8.24
|
99
|
+
signing_key:
|
100
|
+
specification_version: 3
|
101
|
+
summary: Allows you to securely execute shell code with exceptions on errors and timeouts
|
102
|
+
test_files:
|
103
|
+
- test/shellex_test.rb
|