lua-shell-games
Execute shell commands while capturing their output and exit codes. Includes helpers for performing shell escaping/quoting.
$ opm get GUI/lua-shell-games
shell-games
[!Circle CI](https://circleci.com/gh/GUI/lua-shell-games)
A Lua library to help execute shell commands more easily and safely.
Easily execute shell commands, while capturing the command's output and exit code. Includes compatibility across versions of Lua, LuaJIT, and OpenResty where
io.popen
may not return exit codes (pre Lua 5.2 behavior).Utilities to quote and escape shell arguments for safer, less error-prone execution.
When executing shell commands, shell-games wraps either os.execute
or io.popen
(depending on whether the output is being captured).
OpenResty Note: If using shell-games with OpenResty, be aware that executing shell commands with this library is a blocking operation. This may or may not be okay depending on the nginx phase (blocking in init
is okay) and your application's requirements. You may want to consider alternatives like lua-resty-shell or lua-resty-exec if blocking is a concern.
Installation
Via LuaRocks:
luarocks install shell-games
Or via OPM:
opm get GUI/lua-shell-games
Usage
local shell = require "shell-games"
-- Execute a command, with error handling.
local result, err = shell.run({ "touch", "/tmp/hello.txt" })
if err then
print(err)
exit(1)
end
-- Executue a command, capturing both its stdout and stderr output.
local result, err = shell.capture_combined({ "ls", "-l", "/tmp" })
print("Exit code: " .. result["status"])
print("Command output: " .. result["output"])
-- Quoting
shell.quote("ls") -- "ls"
shell.quote("It's happening.") -- [['It'"'"'s happening.']]
shell.quote("$PATH") -- "'$PATH'"
-- Quote and join
shell.join({ "ls", "-l", "/tmp/foo bar" }) -- "ls -l '/tmp/foo bar'"
Compatibility
Tested against:
Lua 5.1
Lua 5.2
Lua 5.3
LuaJIT 2.0
LuaJIT 2.0 compiled with
LUAJIT_ENABLE_LUA52COMPAT
LuaJIT 2.1-beta
LuaJIT 2.1-beta compiled with
LUAJIT_ENABLE_LUA52COMPAT
OpenResty
API
run
syntax: result, err = shell.run(command[, options])
Execute a shell command.
The comamnd
argument must be passed as a table of individual arguments that will be escaped. While this is the recommended way to execute commands, see `run_raw` if you need to execute an arbitrary shell command from a string.
The options
table accepts the following fields:
capture
(boolean): Whether or not to capture the command's output. (default:false
)By default, command output will not be captured, so any stdout/stderr generated by the command will be displayed in the parent process.
If
true
, then the command's stdout will be captured and returned in the result, but stderr will still be printed in the parent process. To capture the command's stderr too, it must be redirected. See `capture_combined` and `capture` for convenience wrappers for more easily running commands while capturing output.chdir
(string): Change the current working directory to this path before executing the command. (default:nil
)env
(table): Set environment variables before executing the command. Accepts a table of environment variable names and values. (default:{}
)stderr
(string): Redirect the command's stderr output to a different path. (default:nil
)stdout
(string): Redirect the command's stdout output to a different path. (default:nil
)umask
(string): Change the process's umask before executing the command. (default:nil
)
If executing the command fails (returning a non-0 exit code), then err
will be a string describing the error. In the case of failure, the result
table will still be returned (with the command's exit code and output reflected in the table).
The returned result
table has the following fields:
command
(string): A string that shows the full command that was executed, after taking into account escaping and the options.status
(integer): The exit code of the command.output
(string): This field is only present if thecapture
option was enabled. If capturing was enabled, then this reflects the output of the command. By default, this will only contains the stdout from the command, and not the stderr. See `capture_combined` and `capture` for convenience wrappers for more easily running commands while capturing output.
-- Basic example
local result, err = shell.run({ "ls", "-l", "/tmp" })
-- Example with options
local result, err = shell.run({ "ls", "-l", "/tmp" }, {
capture = true,
chdir = "/tmp",
env = {
LD_LIBRARY_PATH = "/usr/local/lib",
},
stderr = "/tmp/stderr.log",
stdout = "/tmp/stdout.log",
umask = "0022",
})
capture
syntax: result, err = shell.capture(command[, options])
Execute a shell command, capturing stdout as a string.
This is a convenience wrapper around executing a command with `run` while capturing stdout. It is equivalent to calling run
with the options set to { capture = true }
.
The command
and options
arguments are given in the same format as in `run`.
local result, err = shell.capture({ "ls", "-l", "/tmp" })
capture_combined
syntax: result, err = shell.capture_combined(command[, options])
Execute a shell command, capturing both stdout and stderr as a single string.
This is a convenience wrapper around executing a command with `run` while capturing both stdout and stderr (by redirecting stderr to stdout). It is equivalent to calling run
with the options set to { capture = true, stderr = "&1" }
.
The command
and options
arguments are given in the same format as in `run`.
local result, err = shell.capture_combined({ "ls", "-l", "/tmp/non-existent" })
run_raw
syntax: result, err = shell.run_raw(command[, options])
Execute a shell command given as a raw string.
Usually, using `run`, `capture`, or `capture_combined` is recommended, which all accept the command to run as a table of individual arguments (since these handle shell escaping and quoting for you). However, in cases where you need to execute unescaped commands, this run_raw
can be used to directly run a command string verbatim.
The comamnd
argument must be passed as a string. Be sure to handle any escaping manually within this string.
The options
argument is given in the same format as in `run`.
local result, err = shell.run_raw("echo $PATH", {
capture = true,
})
print(result["output"])
quote
syntax: quoted_string = shell.quote(str)
Return a shell-escaped version of the string. The escaped string can safely be used as one token in a shell command.
shell.quote("ls") -- "ls"
shell.quote("It's happening.") -- [['It'"'"'s happening.']]
shell.quote("$PATH") -- "'$PATH'"
join
syntax: quoted_string = shell.join(table)
Accepts a table of individual command arguments, which will be escaped (using `quote`) and joined together by spaces. Suitable for turning a list of command arguments into a single command string.
shell.join({ "ls", "-l", "/tmp/foo bar" }) -- "ls -l '/tmp/foo bar'"
Development
After checking out the repo, Docker can be used to run the test suite:
docker-compose run --rm app make test
Release Process
To release a new version to LuaRocks and OPM:
Ensure
CHANGELOG.md
is up to date.Update the
_VERSION
inlib/shell-games.lua
.Move the rockspec file to the new version number (
git mv shell-games-X.X.X-1.rockspec shell-games-X.X.X-1.rockspec
), and update theversion
andtag
variables in the rockspec file.Commit and tag the release (
git tag -a vX.X.X -m "Tagging vX.X.X" && git push origin vX.X.X
).Run
make release VERSION=X.X.X
.
Authors
Nick Muerdter
License
mit
Versions
-
GUI/lua-shell-games 1.1.0Execute shell commands while capturing their output and exit codes. Includes helpers for performing shell escaping/quoting. 2019-09-30 00:42:38
-
GUI/lua-shell-games 1.0.2Execute shell commands while capturing their output and exit codes. Includes helpers for performing shell escaping/quoting. 2019-09-29 16:31:02
-
GUI/lua-shell-games 1.0.1Execute shell commands while capturing their output and exit codes. Includes helpers for performing shell escaping/quoting. 2019-05-01 03:24:52
-
GUI/lua-shell-games 1.0.0Execute shell commands while capturing their output and exit codes. Includes helpers for performing shell escaping/quoting. 2018-12-11 03:49:47