shell-executer 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|