Frontkick is a gem to execute a command and obtain exit_code, stdout, stderr simply.
What is This For?
Ruby's Kernel.#system
method does not return STDOUT and STDERR.
Ruby's back quote (``) returns STDOUT, but does not return STDERR.
With frontkick, you can easily get the exit code, STDOUT, and STDERR.
USAGE
gem install frontkick
Basic Usage
result = Frontkick.exec("echo *")
puts result.successful? #=> true if exit_code is 0
puts result.success? #=> alias to successful?, for compatibility with Process::Status
puts result.stdout #=> stdout output of the command
puts result.stderr #=> stderr output of the command
puts result.exit_code #=> exit_code of the command
puts result.status #=> alias to exit_code
puts result.exitstatus #=> alias to exit_code, for compatibility with Process::Status
puts result.duration #=> the time used to execute the command
No Shell
String argument
When the first argument is a String, the command is executed via shell if the command string includes meta characters of shell such as:
* ? {} [] <> () ~ & | \ $ ; ' ` " \n
otherwise, the command is not executed via shell.
result = Frontkick.exec("echo foo") # no shell
result = Frontkick.exec("echo *") # with shell
The process tree for the latter (with shell) will be like:
ruby
└─ sh -c
└── echo *
Array argument
When the first argument is an Array, The command is not executed via a shell. Note that shell wildcards are not available with this way.
result = Frontkick.exec(["echo", "*"]) #=> echo the asterisk character
The process tree will be like:
ruby
└─ echo
NOTE: This no shell interface is similar to IO.popen which work as:
IO.popen(['echo', '*'])
but different with Kernel.spawn, or Open3.popen3 which work as:
spawn('echo', '*')
Environment Variables
You can pass environment variables as a hash for the 1st argument as spawn.
result = Frontkick.exec({"FOO"=>"BAR"}, ["echo", "*"])
Dry Run Option
result = Frontkick.exec(["echo", "*"], dry_run: true)
puts result.stdout #=> echo \*
Timeout Option
Frontkick.exec("sleep 2 && ls /hoge", timeout: 1) # raises Frontkick::Timeout
The default signal that is sent to the command is SIGINT
.
You can change the signal as below.
Frontkick.exec("sleep 2 && ls /hoge", timeout: 1, timeout_kill_signal: 'SIGTERM') # raises Frontkick::Timeout
not to kill timeouted process
Frontkick.exec("sleep 2 && ls /hoge", timeout: 1, timeout_kill: false) # raises Frontkick::Timeout
Exclusive Option
Prohibit another process to run a command concurrently
Frontkick.exec("sleep 2 && ls /hoge", exclusive: "/tmp/frontkick.lock") # raises Fontkick::Locked if locked
If you prefer to be blocked:
Frontkick.exec("sleep 2 && ls /hoge", exclusive: "/tmp/frontkick.lock", exclusive_blocking: true)
Redirect Options (:out and :err)
Frontkick.exec(["ls /something_not_found"], out: 'stdout.txt', err: 'stderr.txt')
This redirects STDOUT and STDERR into files. In this case, result.stdout, and result.stderr are the given filename.
out = File.open('stdout.txt', 'w').tap {|fp| fp.sync = true }
err = File.open('stderr.txt', 'w').tap {|fp| fp.sync = true }
Frontkick.exec(["ls /something_not_found"], out: out, err: err)
You can also give IO objects. In this case, result.stdout, and result.stderr are the given IO objects.
Popen2e Option (Get stdout and stderr together)
result = Frontkick.exec("echo foo; ls /something_not_found", popen2e: true)
puts result.stdout #=>
foo
ls: /something_not_found: No such file or directory
Note that stdout
contains contents of both stdout
and stderr
in this case.
Other Popen3 Options (such as :chdir)
Other options such as :chdir are treated as options of Open3.#popen3
(or Open3.#popen2e
for the case of popen2e: true
)
Kill Child Process
Although sending a signal to a kicked child process directly causes no problem (frontkick process can take care of it), sending a signal to a frontkick process may cause a problem that a child process becomes an orphan process.
To kill your frontkick process with its child process correctly, send a signal to their process group as
kill -TERM -{PGID}
You can find PGID like ps -eo pid,pgid,command
.
If you can not take such an approach by some reasons (for example, daemontools
, a process management tool,
does not support to send a signal to a process group), handle signal by yourself as
Frontkick.exec(["sleep 100"]) do |wait_thr|
pid = wait_thr.pid
trap :INT do
Process.kill(:TERM, pid)
exit 130
end
trap :TERM do
Process.kill(:TERM, pid)
exit 143
end
end
More sophisticated example is available at ./example/kill_child.rb
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request
Copyright
Copyright (c) 2013 Naotoshi SEO. See LICENSE for details.