This framework is intended to help on easy bash script development. It is totally modularized. It helps you developing new Bash Script programs by forcing you to modularize and organize your code in functions, so that your program can be tested.
You can clone the bang repository in any path and run make install
. For instance,
git clone git://github.com/bangsh/bangsh.git
cd bangsh
sudo make prefix=/usr/local install
Basically, bang.sh
allows you to run any bash file. A "hello world" program
would be like this:
$ echo 'echo "Hello World"' > hello_world.sh
$ bang run hello_world.sh
Bang projects are an organized way of placing your files. In order to create a new project, you can just run:
bang new my_project
This command will create a directory called my_project/
. There will be some
directories which are intended to place some specific files.
- my_project
- modules/
- tasks/
- tests/
My project is a executable file that can be run as ./my_project
or with bang run my_project
.
modules/
and tasks/
are where you place your files. All files in the modules/
path is
automatically loaded, so you can define any function in there, that it will be available in my_project
file.
After sourcing the bangsh.sh
file, several functions namespaced by b
will be available, for instance:
- use
b.module.require unittest
to require modules from bangsh or from your modules folder - use
b.get bang.working_dir
to get information, like where the command was called from:
In the next sections there's more detailed information with some examples that will let you understand how the b
namespace is used.
A module is a bunch of functions that have a certain domain. It works like a namespace for aggregating functions. The general idea is to have it isolated, so it could be copied and pasted into another project in a such way it would not rely on any other dependency but Bang, for example:
# modules/my_first_module.sh
function my_first_module_says () {
echo "My first module says: $*"
}
Now, you can use the module in your executable file:
#!/usr/bin/env bang run
my_first_module_says "Hey!"
Remember that exactly like your own modules, those included with bang by default must be required before using them. e.g: b.module.require path
A task is like an action your executable will perform. It is how bang new
and bang test
work.
Every task file should include at least one function (the entrypoint) following the pattern:
# tasks/<name>.sh
function btask.<name>.run () {
# code to run on 'yourprogram <name>'
}
Additionally, as a convention, the other functions defined in the task files should be named with a preceding underscore, e.g:
function _create_module_path () {
# task helper functions start with _
}
This is useful to distinguish "where" a given function comes from and to avoid name collisions with executables in the path.
To see more about tasks, check bang's executable to get an idea of how to use the task module in the main executable of your program as a way to create subcommands.
Keep in mind that you can trigger tasks from withing other tasks, allowing you to create nested subcommands for very expressive CLI tools. Related to this, the opt
module allows you to have a configuration layer for each task, allowing to have this king of calls: bang test --test-specific-option
and bang new --new-task-option
The unittest module provides useful functions for testing. In order to peform assertions, you can use the following functions:
b.unittest.assert_error
: Asserts a function exit code is 1b.unittest.assert_equal
: Asserts a function output is the same as requiredb.unittest.assert_raise
: Asserts a function will raise a given exceptionb.unittest.assert_success
: Asserts a function exit code is zero
There are a few extra functions for more complex scenarios, you can learn about them in the unittest module source
There's a great test task used by the bang binary itself. It prints colors and have some nice defaults that you can use in your project during development. Simply execute the bang test
command inside your project's root path. If you put that path in your PATH env var, you can simply run bang test:
$ exprot PATH="$PATH:/path/to/bang.sh/bin/"
$ cd my_project/
$ bang test
Provides three helpers to work with strings: replace, part and trim. You can see details on how to use these in the http://bangsh.com/api/#String
Exposes functions to simplify common checks on paths, like: doest this file exists? The API docs can be found in http://bangsh.com/api/#Path
Helps you parse options. You can define flags, option aliases and define required arguments. The module can generate usage information if a required argument was missing. You can see the code in the parsing arguments example or check the source that is pretty expressive about the exposed API.
By sourcing bang.sh you get by default some useful utils, e.g:
- Check if a value is inside an array:
in_array? "one" (one two)
- Check for a key in an associative hash:
declare -A TEST_HASH=(["foo"]="bar"); key_exists? "foo" TEST_HASH
- Check if something is a function:
is_function? foo
- Raise and error and exit with code 2:
b.abort
- Print to stderr
print_e
- Sanitize an argument with
sanitize_arg
this is useful to remove semicolons, pipes, ampersands, etc. that could lead to code injections. Examples can be found in the bang tests - Escape argument:
escape_arg
(turns -- into --) - Check whether the argument is a valid module:
is_module?
All this helpers together with others (like the global scope variables), are defined in the bang.sh source
If your program uses external dependencies you can define them and execute a fallback function if the command's missing. For example, check if git is present and print the confirmation or run print_how_to_install_git
otherwise:
b.try.do b.depends_on git
b.catch DependencyNotMetException print_how_to_install_git
b.try.end
echo 'You have git installed!'
You can handle custom exceptions inside your functions: b.raise FileNotFoundException
. You can make WhateverException
name you want!
To work with exceptions you define the function to run, the functions that catch specific exceptions and, optionally, a finally
clause that gets executed even if no exception was raised. Here's an example:
b.try.do run "$1"
b.catch FileNotFoundException file_not_found
b.catch IDontKnowException i_dont_know_what_it_is
b.finally try_again
b.try.end
In the previos example i_dont_know_what_it_is
and file_not_found
are simply functions triggered on the applicable cases. try_again
however will always run.
To store information accessible from anywhere in the program during it's excution, you can use b.set
using the format: b.set <registry>.<key> <value>
.
As a convention, the bang
registry is reserved for things like: b.set bang.working_dir $(pwd)
(allows you to know exactly from what folder the program executiong began).
The bang registry also stores information about added tasks, e.g: b.get bang.tasks.taskname
would return the description of taskname
.
You can use other "registries" for your own programs, e.g: b.set "myprogram.version" "1.0"
Here's a list of useful methods related to globally scoped data storage:
b.set
b.get
b.is_set?
b.unset