shell-executer 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/README +61 -0
- data/Rakefile +68 -0
- data/lib/shell/executer.rb +132 -0
- data/test/test_executer.rb +50 -0
- metadata +80 -0
data/README
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
= shell-executer
|
2
|
+
|
3
|
+
Shell::Executer provides an easy and robust way to execute shell commands.
|
4
|
+
The stdout and stderr data can be read completely after the execution or
|
5
|
+
in chunks during the execution. If the data is processed in chunks this could
|
6
|
+
be done by line or by char.
|
7
|
+
|
8
|
+
Command execution is done utilizing open4[http://rubyforge.org/projects/codeforpeople/].
|
9
|
+
|
10
|
+
by {Holger Kohnen}[http://www.holgerkohnen.de]
|
11
|
+
|
12
|
+
== Download and Installation
|
13
|
+
|
14
|
+
You can download shell-executer from here[http://rubyforge.org/projects/shell-executer/] or install it with the following command.
|
15
|
+
|
16
|
+
$ gem install shell-executer
|
17
|
+
|
18
|
+
== License
|
19
|
+
|
20
|
+
You may use, copy and redistribute this library under the same terms as Ruby itself (see http://www.ruby-lang.org/en/LICENSE.txt).
|
21
|
+
|
22
|
+
== Usage
|
23
|
+
require 'shell/executer.rb'
|
24
|
+
|
25
|
+
# execute and read standard output
|
26
|
+
Shell.execute('echo hello').stdout # => "hello\n"
|
27
|
+
|
28
|
+
# execute and read error output
|
29
|
+
Shell.execute('echo >&2 error').stderr # => "error\n"
|
30
|
+
|
31
|
+
# execute and check if the command exited with success
|
32
|
+
Shell.execute('ls /tmp').success? # => true
|
33
|
+
Shell.execute('ls /not_existing').success? # => false
|
34
|
+
|
35
|
+
# execute and process every line of output with block
|
36
|
+
Shell.execute('echo hello; echo world') {|stdout| print stdout }
|
37
|
+
# => "hello\n"
|
38
|
+
# => "world\n"
|
39
|
+
|
40
|
+
# execute and process every char of output with block
|
41
|
+
Shell.execute('echo 42', :mode => :chars) {|stdout| puts stdout }
|
42
|
+
# => "4\n"
|
43
|
+
# => "2\n"
|
44
|
+
|
45
|
+
# execute and process every line of stdout or stderr
|
46
|
+
Shell.execute('echo hello; echo >&2 error') do |stdout, stderr|
|
47
|
+
if stdout
|
48
|
+
print 'OUT> %s' % stdout
|
49
|
+
else
|
50
|
+
print 'ERR> %s' % stderr
|
51
|
+
end
|
52
|
+
end
|
53
|
+
# => "OUT> hello\n"
|
54
|
+
# => "ERR> error\n"
|
55
|
+
|
56
|
+
# execute and throw an exception if the process exited without success
|
57
|
+
begin
|
58
|
+
Shell.execute!('ls /not_existing')
|
59
|
+
rescue RuntimeError => e
|
60
|
+
print e.message # <= prints stderr
|
61
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
require 'rake/contrib/sshpublisher'
|
6
|
+
|
7
|
+
TITLE = 'shell-executer'
|
8
|
+
DESCRIPTION = <<-EOS
|
9
|
+
Shell::Executer provides an easy and robust way to execute shell commands.
|
10
|
+
The stdout and stderr data can be read completly after the execution or
|
11
|
+
in chunks during the execution.
|
12
|
+
EOS
|
13
|
+
AUTHOR = 'Holger Kohnen'
|
14
|
+
AUTHOR_EMAIL = 'h.kohnen@gmail.com'
|
15
|
+
GEM_VERSION = '1.0.0'
|
16
|
+
HOMEPAGE = "http://#{TITLE}.rubyforge.org/"
|
17
|
+
PROJECT_NAME = TITLE
|
18
|
+
|
19
|
+
task :default => :test
|
20
|
+
|
21
|
+
Rake::TestTask.new(:test) do |t|
|
22
|
+
t.test_files = FileList[
|
23
|
+
'test/test*.rb'
|
24
|
+
]
|
25
|
+
t.warning = true
|
26
|
+
t.verbose = false
|
27
|
+
end
|
28
|
+
|
29
|
+
desc 'Generate RDoc'
|
30
|
+
Rake::RDocTask.new do |task|
|
31
|
+
task.main = 'README'
|
32
|
+
task.title = TITLE
|
33
|
+
task.rdoc_dir = 'doc'
|
34
|
+
task.options << "--line-numbers" << "--inline-source"
|
35
|
+
task.rdoc_files.include('README', 'lib/**/*.rb')
|
36
|
+
end
|
37
|
+
|
38
|
+
specification = Gem::Specification.new do |s|
|
39
|
+
s.name = TITLE
|
40
|
+
s.summary = DESCRIPTION
|
41
|
+
s.version = GEM_VERSION
|
42
|
+
s.author = AUTHOR
|
43
|
+
s.description = DESCRIPTION
|
44
|
+
s.homepage = HOMEPAGE
|
45
|
+
s.rubyforge_project = PROJECT_NAME
|
46
|
+
|
47
|
+
s.has_rdoc = true
|
48
|
+
s.extra_rdoc_files = ['README']
|
49
|
+
s.rdoc_options <<
|
50
|
+
'--title' << TITLE << '--main' << 'README' << '--line-numbers' << "--inline-source"
|
51
|
+
|
52
|
+
s.email = AUTHOR_EMAIL
|
53
|
+
s.files = FileList['{lib,test}/**/*.rb', '[A-Z]*$', 'Rakefile'].to_a
|
54
|
+
s.add_dependency('open4')
|
55
|
+
s.add_dependency('expectations')
|
56
|
+
end
|
57
|
+
Rake::GemPackageTask.new(specification) do |package|
|
58
|
+
package.need_zip = false
|
59
|
+
package.need_tar = false
|
60
|
+
end
|
61
|
+
|
62
|
+
desc "Upload RDoc to RubyForge"
|
63
|
+
task :publish_rdoc do
|
64
|
+
Rake::Task[:rdoc].invoke
|
65
|
+
Rake::SshDirPublisher.new(
|
66
|
+
"holgerkohnen@rubyforge.org",
|
67
|
+
"/var/www/gforge-projects/#{PROJECT_NAME}", "doc").upload
|
68
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'open4'
|
2
|
+
|
3
|
+
module Shell
|
4
|
+
|
5
|
+
class << self
|
6
|
+
# Shorthand for Shell::Executer#execute. For +options+ see: Shell::Executer.new.
|
7
|
+
def execute(command, options={}, &block)
|
8
|
+
Executer.new(options).execute(command, &block)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Shorthand for Shell::Executer#execute!. For +options+ see: Shell::Executer.new.
|
12
|
+
def execute!(command, options={}, &block)
|
13
|
+
Executer.new(options).execute!(command, &block)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Executer
|
18
|
+
# Holds last executed commands standard output if executed without block.
|
19
|
+
attr_reader :stdout
|
20
|
+
|
21
|
+
# Holds last executed commands error output.
|
22
|
+
attr_reader :stderr
|
23
|
+
|
24
|
+
# Configuration options:
|
25
|
+
# * <tt>:mode</tt> - Specifies the output mode.
|
26
|
+
# Can be <tt>:chars</tt> or <tt>:lines</tt>.
|
27
|
+
# In lines-mode every line of the output is passed to the block.
|
28
|
+
# In chars-mode every char of the output is passed to the block.
|
29
|
+
# (default is: <tt>:lines</tt>)
|
30
|
+
def initialize(options={})
|
31
|
+
@options = {
|
32
|
+
:mode => :lines # :chars|:lines <- output mode
|
33
|
+
}.merge(options)
|
34
|
+
|
35
|
+
@reader = (@options[:mode] == :chars) ? proc {|s| s.getc } : proc {|s| s.gets }
|
36
|
+
@mutex = Mutex.new
|
37
|
+
|
38
|
+
@stdout = ''
|
39
|
+
@stderr = ''
|
40
|
+
@success = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
# :call-seq: execute(command) -> Shell::Executer
|
44
|
+
# execute(command) {|stdout| ...} -> Shell::Executer
|
45
|
+
# execute(command) {|stdout, stderr| ...} -> Shell::Executer
|
46
|
+
#
|
47
|
+
# Executes <tt>command</tt> and returns after execution.
|
48
|
+
#
|
49
|
+
# In the first form(<tt>execute(command)</tt>) the +command+ ist executed
|
50
|
+
# and the +stdout+, +stderr+ and +success+ attributes are populated.
|
51
|
+
#
|
52
|
+
# In the second and third form the +stdout+ attribute is *not* populated. The +stdout+
|
53
|
+
# is passed to the specified block instead. The block is invoked for
|
54
|
+
# every char or line of output depending on the specified <tt>options</tt>.
|
55
|
+
#
|
56
|
+
# In the second form only the stdout is passed to the block.
|
57
|
+
#
|
58
|
+
# In the third form either stdout or stderr is filled. The opposite is allways nil.
|
59
|
+
# # Prefix output of rsync
|
60
|
+
# Shell::Executer.new.execute('rsync -avz /source/ /dest/') do |stdout, stderr|
|
61
|
+
# if stderr
|
62
|
+
# print 'ERR> ' + stderr
|
63
|
+
# else
|
64
|
+
# print 'OUT> ' + stdout
|
65
|
+
# end
|
66
|
+
# end
|
67
|
+
def execute(command, &block)
|
68
|
+
@stdout = ''
|
69
|
+
@stderr = ''
|
70
|
+
@success = nil
|
71
|
+
|
72
|
+
begin
|
73
|
+
threads = []
|
74
|
+
pid, stdin, stdout, stderr = Open4::popen4(command)
|
75
|
+
[[:stdout, stdout], [:stderr, stderr]].each do |type, stream|
|
76
|
+
threads << Thread.new(type, stream) do |type, stream|
|
77
|
+
Thread.current.abort_on_exception = true
|
78
|
+
while data = @reader.call(stream)
|
79
|
+
data = ((@options[:mode] == :chars) ? data.chr : data)
|
80
|
+
@mutex.synchronize do
|
81
|
+
if type == :stdout
|
82
|
+
if block_given?
|
83
|
+
if block.arity == 2
|
84
|
+
yield data, nil
|
85
|
+
else
|
86
|
+
yield data
|
87
|
+
end
|
88
|
+
else
|
89
|
+
@stdout += data
|
90
|
+
end
|
91
|
+
else
|
92
|
+
yield nil, data if block_given? && block.arity == 2
|
93
|
+
@stderr += data
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
ignored, status = Process::waitpid2 pid
|
101
|
+
@success = (status.exitstatus == 0)
|
102
|
+
|
103
|
+
ensure
|
104
|
+
threads.each(&:join)
|
105
|
+
stdin.close if stdin
|
106
|
+
stdout.close if stdout
|
107
|
+
stderr.close if stderr
|
108
|
+
end
|
109
|
+
|
110
|
+
self
|
111
|
+
end
|
112
|
+
|
113
|
+
# :call-seq: execute!(command) -> Shell::Executer
|
114
|
+
# execute!(command) {|stdout| ...} -> Shell::Executer
|
115
|
+
# execute!(command) {|stdout, stderr| ...} -> Shell::Executer
|
116
|
+
#
|
117
|
+
# Same as execute, but throws <tt>RuntimeError</tt> containing stderr if <tt>command</tt>
|
118
|
+
# exited without success.
|
119
|
+
def execute!(command, &block)
|
120
|
+
execute(command, &block)
|
121
|
+
raise stderr unless success?
|
122
|
+
self
|
123
|
+
end
|
124
|
+
|
125
|
+
# Returns true if the last command was executed successfully.
|
126
|
+
def success?
|
127
|
+
@success
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'expectations'
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '../lib/shell/executer.rb'))
|
3
|
+
|
4
|
+
Expectations do
|
5
|
+
|
6
|
+
expect 'stdout' do
|
7
|
+
Shell::Executer.new.execute('echo stdout').stdout.strip
|
8
|
+
end
|
9
|
+
|
10
|
+
expect 'stderr' do
|
11
|
+
Shell::Executer.new.execute('echo >&2 stderr').stderr.strip
|
12
|
+
end
|
13
|
+
|
14
|
+
expect true do
|
15
|
+
Shell::Executer.new.execute('echo 42').success?
|
16
|
+
end
|
17
|
+
|
18
|
+
expect false do
|
19
|
+
Shell::Executer.new.execute('ls /does_not_exist').success?
|
20
|
+
end
|
21
|
+
|
22
|
+
expect RuntimeError do
|
23
|
+
Shell::Executer.new.execute!('ls /does_not_exist')
|
24
|
+
end
|
25
|
+
|
26
|
+
expect ["std\n", "out\n"] do
|
27
|
+
r = []
|
28
|
+
Shell::Executer.new.execute('echo std; echo out') do |stdout, stderr|
|
29
|
+
r << stdout
|
30
|
+
end
|
31
|
+
r
|
32
|
+
end
|
33
|
+
|
34
|
+
expect ["4", "2"] do
|
35
|
+
r = []
|
36
|
+
Shell::Executer.new(:mode => :chars).execute('echo -n 42') do |stdout, stderr|
|
37
|
+
r << stdout.strip
|
38
|
+
end
|
39
|
+
r
|
40
|
+
end
|
41
|
+
|
42
|
+
expect 'stdout' do
|
43
|
+
Shell.execute('echo stdout').stdout.strip
|
44
|
+
end
|
45
|
+
|
46
|
+
expect RuntimeError do
|
47
|
+
Shell.execute!('ls /does_not_exist')
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shell-executer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Holger Kohnen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-04-30 00:00:00 +02:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: open4
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: expectations
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
description: Shell::Executer provides an easy and robust way to execute shell commands. The stdout and stderr data can be read completly after the execution or in chunks during the execution.
|
36
|
+
email: h.kohnen@gmail.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- README
|
43
|
+
files:
|
44
|
+
- lib/shell/executer.rb
|
45
|
+
- test/test_executer.rb
|
46
|
+
- Rakefile
|
47
|
+
- README
|
48
|
+
has_rdoc: true
|
49
|
+
homepage: http://shell-executer.rubyforge.org/
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options:
|
52
|
+
- --title
|
53
|
+
- shell-executer
|
54
|
+
- --main
|
55
|
+
- README
|
56
|
+
- --line-numbers
|
57
|
+
- --inline-source
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
version:
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: "0"
|
71
|
+
version:
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
rubyforge_project: shell-executer
|
75
|
+
rubygems_version: 1.3.1
|
76
|
+
signing_key:
|
77
|
+
specification_version: 2
|
78
|
+
summary: Shell::Executer provides an easy and robust way to execute shell commands. The stdout and stderr data can be read completly after the execution or in chunks during the execution.
|
79
|
+
test_files: []
|
80
|
+
|