Cocaine
A small library for doing (command) lines.
Usage
The basic, normal stuff:
line = Cocaine::CommandLine.new("echo", "hello 'world'")
line.command # => "echo hello 'world'"
line.run # => "hello world\n"
Interpolated arguments:
line = Cocaine::CommandLine.new("convert", ":in -scale :resolution :out")
line.command(:in => "omg.jpg",
:resolution => "32x32",
:out => "omg_thumb.jpg")
# => "convert 'omg.jpg' -scale '32x32' 'omg_thumb.jpg'"
It prevents attempts at being bad:
line = Cocaine::CommandLine.new("cat", ":file")
line.command(:file => "haha`rm -rf /`.txt") # => "cat 'haha`rm -rf /`.txt'"
line = Cocaine::CommandLine.new("cat", ":file")
line.command(:file => "ohyeah?'`rm -rf /`.ha!") # => "cat 'ohyeah?'\\''`rm -rf /`.ha!'"
You can ignore the result:
line = Cocaine::CommandLine.new("noisy", "--extra-verbose", :swallow_stderr => true)
line.command # => "noisy --extra-verbose 2>/dev/null"
# ... and on Windows...
line.command # => "noisy --extra-verbose 2>NUL"
If your command errors, you get an exception:
line = Cocaine::CommandLine.new("git", "commit")
begin
line.run
rescue Cocaine::ExitStatusError => e
e. # => "Command 'git commit' returned 1. Expected 0"
end
If your command might return something non-zero, and you expect that, it's cool:
line = Cocaine::CommandLine.new("/usr/bin/false", "", :expected_outcodes => [0, 1])
begin
line.run
rescue Cocaine::ExitStatusError => e
# => You never get here!
end
You don't have the command? You get an exception:
line = Cocaine::CommandLine.new("lolwut")
begin
line.run
rescue Cocaine::CommandNotFoundError => e
e # => the command isn't in the $PATH for this process.
end
But don't fear, you can specify where to look for the command:
Cocaine::CommandLine.path = "/opt/bin"
line = Cocaine::CommandLine.new("lolwut")
line.command # => "lolwut", but it looks in /opt/bin for it.
You can even give it a bunch of places to look:
FileUtils.rm("/opt/bin/lolwut")
File.open('/usr/local/bin/lolwut') {|f| f.write('echo Hello') }
Cocaine::CommandLine.path = ["/opt/bin", "/usr/local/bin"]
line = Cocaine::CommandLine.new("lolwut")
line.run # => prints 'Hello', because it searches the path
Or just put it in the command:
line = Cocaine::CommandLine.new("/opt/bin/lolwut")
line.command # => "/opt/bin/lolwut"
You can see what's getting run. The 'Command' part it logs is in green for visibility!
line = Cocaine::CommandLine.new("echo", ":var", :logger => Logger.new(STDOUT))
line.run(:var => "LOL!") # => Logs this with #info -> Command :: echo 'LOL!'
Or log every command:
Cocaine::CommandLine.logger = Logger.new(STDOUT)
Cocaine::CommandLine.new("date").run # => Logs this -> Command :: date
POSIX Spawn
You can potentially increase performance by installing the posix-spawn
gem. This gem can keep your
application's heap from being copied when forking command line
processes. For applications with large heaps the gain can be
significant. To include posix-spawn
, simply add it to your Gemfile
or,
if you don't use bundler, install the gem.
Runners
Cocaine will attempt to choose from among 3 different ways of running commands.
The simplest is using backticks, and is the default in 1.8. In Ruby 1.9, it
will attempt to use Process.spawn
. And, as mentioned above, if the
posix-spawn
gem is installed, it will attempt to use that. If for some reason
one of the .spawn
runners don't work for you, you can override them manually
by setting a new runner, like so:
Cocaine::CommandLine.runner = Cocaine::BackticksRunner.new
And if you really want to, you can define your own Runner, though I can't imagine why you would.
JRuby Caveat
If you get Error::ECHILD
errors and are using JRuby, there is a very good
chance that the error is actually in JRuby. This was brought to our attention
in https://github.com/thoughtbot/cocaine/issues/24 and probably fixed in
http://jira.codehaus.org/browse/JRUBY-6162. You will want to use the
BackticksRunner
if you are unable to update JRuby.
REE
So, here's the thing about REE: The specs that involve timeouts don't work there. Not because the logic is unsound, but because the command runs really slowly. The test passes -- eventually. This was verified using an external debugger: the process that REE kicks off in the tests reads and writes surprisingly slowly. For this reason, we cannot recommend using Cocaine with REE anymore.
It's not something we like, so if anyone has any insight into this problem, we'd love to hear about it. But, for the time being, we'll consider it more appropriate to just not use it anymore. Upgrade to 1.9.3, people.
Feedback
Security concerns must be privately emailed to [email protected].
Question? Idea? Problem? Bug? Comment? Concern? Like using question marks?
License
Copyright 2011-2012 Jon Yurek and thoughtbot, inc. This is free software, and may be redistributed under the terms specified in the LICENSE file.