Gabr.sh
Installation
Try out as portable file
$ wget https://raw.githubusercontent.com/nicobrinkkemper/gabr.sh/master/gabr.sh$ source ./gabr.sh
Install with git
$ git clone https://github.com/nicobrinkkemper/gabr.sh.git gabr$ cd gabr$ npm link
Install with npm
$ npm install --save-dev gabr$ npm link gabr
If you want to run
gabr
as a local function, try. gabr
What is gabr.sh
Gabr is a Bash function designed to call other Bash functions. Gabr takes arguments and will try to turn that in to a function call. Gabr takes the path of least resistance towards a function call. Let's illustrate that with a flowchart.
This flowchart doesn't show error cases. These cases are mostly when the last argument did not result in the execution of a real function.
Let's illustrate further with a code example.
$ echo "\if [ $# -eq 0 ]; then printf '%s\n' 'Usage: gabr hello world' >&2fifunction world() { printf '%s\n' 'Hello World.' >&2}" > ./hello.sh$ gabr helloUsage: gabr hello world$ gabr hello worldHello World.
This is great for a file that contains a collection of functions
A different approach would be:
$ echo "\function usage() { printf '%s\n' 'Usage: gabr hello world'}function world() { printf '%s\n' 'Hello World.' >&2}" > ./hello.sh$ gabr helloUsage: gabr hello world$ gabr hello usageUsage: gabr hello world$ gabr hello worldHello World.
See functions for a different variation of this
Lastly, a hello
function can be defined to catch all arguments after the hello
argument.
$ echo "\function hello(){ [ \$# -eq 0 ] && printf '%s\n' 'Usage: gabr hello <string>' >&2 && return 1 printf '%s\n' "Hello \$1." >&2}" > ./hello.sh$ gabr helloUsage: gabr hello <string>$ gabr hello worldHello world.
This makes a function easily reachable but limits a file to one function.
To omit this, it is alowed -but not adviced- to call gabr
recursively like so:
$ echo "\function hello(){ if [ \$# -eq 0 ]; then printf '%s\n' 'Usage: gabr hello world' >&2 return 1 fi gabr \$@}function world(){ printf '%s\n' "Hello World." >&2}" > ./hello.sh$ gabr helloUsage: gabr hello <string>$ gabr hello worldHello world.
It is not adviced because it is a unnecessary dependency in most cases.
There are some usecases for it, namely debugging. See example/debug.sh
for
a example of this.
Why use gabr.sh?
Use it when you want to make a simple API to automate stuff you care about. Consider the following commands to delete a tag with git:
git tag -d 1.0.1git push origin :refs/tags/1.0.1
This is hard to remember next time you'd need it. It's also hard to delete multiple tags because you'd need to shift your cursor around to change the tags. Now consider the following function.
set -eu
Let's say it's saved in
./git.sh
This is easy to forget too, but one can refresh memory by looking at the file.
To run this function like gabr
would, one could simply write:
$
But doing it like this is hard to communicate and prone to human error.
With gabr
a more direct api emerges to do these kind of things:
$ gabr git deleteTag 1.0.1
With this basic concept, all functions you see in .sh files will be available through a simple api that is easy to communicate. Just type in what you see.
Variables
GABR_STRICT_MODE (default:true)
A variable called GABR_STRICT_MODE
may be used to toggle the following snippet:
set -eEuo pipefaillocal IFS=$'\n\t'trap 'return $?' ERR SIGINT
This snippet will run once inside the subshell where the function is called and the file is sourced. Let's go over the three lines:
-
set
allows you to change the values of shell options and set the positional parameters, or to display the names and values of shell variables. (reference)- -x Enter debug mode
- -e Exit immediately on errors
- -E Inherit traps
- -u Error on unset variables
- -o pipefail the return value is that of the last error
-
IFS
is a string treated as a list of characters that is used for field splitting. By default, this is set to <space> <tab> <newline>. <space> causes issues when entering arguments that contain spaces, such as sentences. This is whyIFS
is set to <tab> <newline> in strict-mode. (reference) -
If
return
is executed by atrap ERR
handler, the last command used to determine the non-zero status is the last command executed before the trap handler.trap 'return $?' ERR
will ensure the conditions obeyed by the errexit (-e) option for older Bash versions. Furthermore,SIGINT
will be handled the same way, which allows a user to interrupt (ctrl+C) any long running script. (reference)
To opt-out of strict-mode:
$ export GABR_STRICT_MODE=off
export is not needed when running as a local function
GABR_DEBUG_MODE
Setting this variable to a value will turn on debug mode for files and functions.
The gabr
function will do set -x
before and set +x
after every
file source and function call.
$ export GABR_DEBUG_MODE=true
This variable is useful, because it omits gabr
debug info from polluting a users code.
GABR_ROOT
If GABR_ROOT
is set to a value the gabr
function will change directory
to this location on every invocation.
$ export GABR_ROOT=$(git rev-parse --show-toplevel)
This will make files at the root of a git repository accessible to the
gabr
function
This variable is powerful, it will make arguments put in more likely to result
in the same output. Keep in mind that the gabr
function will lose it's flexibility,
it will always run from a fixed location.
GABR_DEFAULT
A global variable called GABR_DEFAULT
may be used to influence the default namespace.
By default this namespace is usage
.
$ export GABR_DEFAULT=index
This will make
index.sh
behave similar to index-files in other programming languages.
This variable is useful, but the default value usage
is probably the way to go.
GABR_EXT
GABR_EXT
may be used to alter the value of ext
. Files with a .sh
extension are
sourced, files without are ran with exec
$ export GABR_EXT=.bash
With the right shebang, any programming language can be called. However, keep in mind
that gabr
also looks for files without a extension. These files will always run with
exec
. For example, see ./example/javascript
$ gabr example javascript helloArguments received: 30 -> .../node/v11.7.0/bin/node1 -> .../gabr/example/javascript2 -> hello
Local variables
Gabr defines the following local variables. These will be defined in sourced files.
variable | type | description | default | Note |
---|---|---|---|---|
default | Name of fallback namespace | usage | May be set by GABR_DEFAULT |
|
usage | Usage string | "Usage: gabr [file] function..." | ||
$default | String printed by fallback function | $usage | See Functions | |
fn | The called function | |||
args | -a | The left-over arguments | () | Available as ${@} in sourced files/functions |
prevArgs | -a | The successful arguments | () | |
file | The sourced file | Will be set to the latest sourced file | ||
dir | The directory of the file | . | Will be relative path from starting point | |
ext | Extension to use source |
.sh | exec is used for files without extension |
|
FUNCNEST | See manual (reference) | 50 | Prohibits overly recursive function calls |
Functions
gabr
does not try to add any public functions except itself. There are some, but these are
prefixed with _
, suggesting they are private (they aren't really). There is also the possibility
that a default function will be generated.
When running the file that contains the gabr
function,
it will be called. In some cases, a default function is generated that is not yours.
The name may be changed with GABR_DEFAULT
. This is not
the goal of gabr
, so it will only be added in these rare cases.
- The argument is the default namespace
- Default function will be generated and called if nothing is found
function usage ()
By default usage
is a important namespace for the gabr
function. usage
behaves
like a exit condition. The argument will always result in a function call, and thus
exit the interal loop. The following snippet shows the last-resort function that will be generated when a usage
function or file is not available.
# ...on invocation local usage="gabr [directory | file] function [arguments] -- A function to call other functions."# ...later, if all else fails
The usage
variable may be altered during file source.
Below snippet will force usage
when the last argument was a file name but not a function name.
This can be useful for filenames that may not contain a function with that name.
A alternative approach would be to just define the usage
function.
It will be called if no arguments are given after the file argument.
echo "npm test" >&2 npm test}
Finally, a default file may be consulted when the
a argument is a directory but not a file. For a example of this, see ./test/usage.sh
or run gabr test
to see it in action.
function $default ()
The namespace for usage
may be altered with GABR_DEFAULT
or simply default
.
A last-resort function and variable will be made for this name instead.
This is done through variable indirection. (reference)
To generate a function with a dynamic name a small eval trick is used. For this reason
the default
variable may not contain special-characters.
default=help printf "help-info-for-this-file"}
This will run the
help
function when no arguments come after a file argument
Flags
The internal loop wil error at any argument that starts with a dash (-). Any argument that comes behind the dash will be printed as a warning to the user. The return code will be 1.
set -- '-' 'Error, something went wrong'