• Stars
    star
    74
  • Rank 414,800 (Top 9 %)
  • Language
    Shell
  • License
    MIT License
  • Created over 8 years ago
  • Updated about 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

ReBash - bash scripting library/framework

ReBash - bash/shell library/framework

Build Status

Motivation

Developing in bash has some serious flaws:

  • scoping - bash functions are always global
  • no exception handling
  • larger projects quickly become non-transparent
  • ...

Features

  • modular import system
  • advanced logging (colors, control stdout/stderr, log levels, ...)
  • error handling (exceptions, try-catch)
  • doc testing inspired by python
  • documentation generation
  • argument parser
  • utility functions

Doc test examples

./doc_test.sh array.sh -v

Gif of doc_test run on the array module

./doc_test.sh

Gif of full doc_test run

./doc_test.sh -v

Gif of full verbose doc_test run with failure

Usage

Source the core module and use core.import to import other modules.

#!/usr/bin/env bash
source path/to/core.sh
core.import <modulename>
core.import <another modulename>
# use modules ...

Installation

Currently only an archlinux package is available at the aur. After installation all rebash files are available under /usr/lib/rebash/. The doc_test and documentation modules are available as /usr/bin/rebash-doc-test and /usr/bin/rebash-documentation.

Module Concept

Modules are single files. The function core.import guarantees that each module is sourced only once. All variables and functions defined inside a module should be prefixed with the module name. E.g. core_import for the function import in module core. Aliases inside the module are used to define public functions and to have a convinient way to distinguish the module namespace from the function (alias core.import="core_import").

A typical minimal module looks like this (with filename mockup.sh):

#!/usr/bin/env bash
source "$(dirname "${BASH_SOURCE[0]}")/core.sh"
core.import logging
mockup_foo() {
    echo foo
}
alias mockup.foo="mockup_foo"

Best practices / coding style

No surprises

Loading modules (i.e. when sourced by the import mechanism) should be side-effect free, so only variable and function definitions should be made at the module level. If the module should be executable, use core.is_main. For example this module does activate exceptions only when run directly, not when being sourced.

#!/usr/bin/env bash
source path/to/core.sh
core.import exceptions
main() {
    exceptions.activate
    # do stuff
}
if core.is_main; then
    main
fi

Testing

Write doc_tests for every module and function. Write the tests before writing the implementation.

Linting with shellcheck

Use shellcheck to tackle common errors and pitfalls in bash.

Generated documentation

Module arguments

The arguments module provides an argument parser that can be used in functions and scripts.

Different functions are provided in order to parse an arguments array.

Example

>>> _() {
>>>     local value
>>>     arguments.set "$@"
>>>     arguments.get_parameter param1 value
>>>     echo "param1: $value"
>>>     arguments.get_keyword keyword2 value
>>>     echo "keyword2: $value"
>>>     arguments.get_flag --flag4 value
>>>     echo "--flag4: $value"
>>>     # NOTE: Get the positionals last
>>>     arguments.get_positional 1 value
>>>     echo 1: "$value"
>>>     # Alternative way to get positionals: Set the arguments array to
>>>     # $arguments_new_arguments
>>>     set -- "${arguments_new_arguments[@]}"
>>>     echo 1: "$1"
>>> }
>>> _ param1 value1 keyword2=value2 positional3 --flag4
param1: value1
keyword2: value2
--flag4: true
1: positional3
1: positional3

Function arguments_get_flag

arguments.get_flag flag [flag_aliases...] variable_name

Sets variable_name to true if flag (or on of its aliases) is contained in the argument array (see arguments.set)

Example

arguments.get_flag verbose --verbose -v verbose_is_set

Tests

>>> arguments.set other_param1 --foo other_param2
>>> local foo bar
>>> arguments.get_flag --foo -f foo
>>> echo $foo
>>> arguments.get_flag --bar bar
>>> echo $bar
>>> echo "${arguments_new_arguments[@]}"
true
false
other_param1 other_param2
>>> arguments.set -f
>>> local foo
>>> arguments.get_flag --foo -f foo
>>> echo $foo
true

Function arguments_get_keyword

arguments.get_keyword keyword variable_name

Sets variable_name to the "value" of keyword the argument array (see arguments.set) contains "keyword=value".

Example

arguments.get_keyword log loglevel

Tests

>>> local foo
>>> arguments.set other_param1 foo=bar baz=baz other_param2
>>> arguments.get_keyword foo foo
>>> echo $foo
>>> echo "${arguments_new_arguments[@]}"
bar
other_param1 baz=baz other_param2
>>> local foo
>>> arguments.set other_param1 foo=bar baz=baz other_param2
>>> arguments.get_keyword foo
>>> echo $foo
>>> arguments.get_keyword baz foo
>>> echo $foo
bar
baz

Function arguments_get_parameter

arguments.get_parameter parameter [parameter_aliases...] variable_name

Sets variable_name to the field following parameter (or one of the parameter_aliases) from the argument array (see arguments.set).

Example

arguments.get_parameter --log-level -l loglevel

Tests

>>> local foo
>>> arguments.set other_param1 --foo bar other_param2
>>> arguments.get_parameter --foo -f foo
>>> echo $foo
>>> echo "${arguments_new_arguments[@]}"
bar
other_param1 other_param2

Function arguments_get_positional

arguments.get_positional index variable_name

Get the positional parameter at index. Use after extracting parameters, keywords and flags.

>>> arguments.set parameter foo --flag pos1 pos2 --keyword=foo
>>> arguments.get_flag --flag _
>>> arguments.get_parameter parameter _
>>> arguments.get_keyword --keyword _
>>> local positional1 positional2
>>> arguments.get_positional 1 positional1
>>> arguments.get_positional 2 positional2
>>> echo "$positional1 $positional2"
pos1 pos2

Function arguments_set

arguments.set argument1 argument2 ...

Set the array the arguments-module is working on. After getting the desired arguments, the new argument array can be accessed via arguments_new_arguments. This new array contains all remaining arguments.

Module array

Function array_filter

Filters values from given array by given regular expression.

>>> local a=(one two three wolf)
>>> local b=( $(array.filter ".*wo.*" "${a[@]}") )
>>> echo ${b[*]}
two wolf

Function array_get_index

Get index of value in an array

>>> local a=(one two three)
>>> array_get_index one "${a[@]}"
0
>>> local a=(one two three)
>>> array_get_index two "${a[@]}"
1
>>> array_get_index bar foo bar baz
1

Function array_slice

Returns a slice of an array (similar to Python).

From the Python documentation: One way to remember how slices work is to think of the indices as pointing between elements, with the left edge of the first character numbered 0. Then the right edge of the last element of an array of length n has index n, for example:

+---+---+---+---+---+---+
| 0 | 1 | 2 | 3 | 4 | 5 |
+---+---+---+---+---+---+
0   1   2   3   4   5   6
-6  -5  -4  -3  -2  -1
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice 1:-2 "${a[@]}")
1 2 3
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice 0:1 "${a[@]}")
0
>>> local a=(0 1 2 3 4 5)
>>> [ -z "$(array.slice 1:1 "${a[@]}")" ] && echo empty
empty
>>> local a=(0 1 2 3 4 5)
>>> [ -z "$(array.slice 2:1 "${a[@]}")" ] && echo empty
empty
>>> local a=(0 1 2 3 4 5)
>>> [ -z "$(array.slice -2:-3 "${a[@]}")" ] && echo empty
empty
>>> local a=(0 1 2 3 4 5)
>>> [ -z "$(array.slice -2:-2 "${a[@]}")" ] && echo empty
empty

Slice indices have useful defaults; an omitted first index defaults to zero, an omitted second index defaults to the size of the string being sliced.

>>> local a=(0 1 2 3 4 5)
>>> # from the beginning to position 2 (excluded)
>>> echo $(array.slice 0:2 "${a[@]}")
>>> echo $(array.slice :2 "${a[@]}")
0 1
0 1
>>> local a=(0 1 2 3 4 5)
>>> # from position 3 (included) to the end
>>> echo $(array.slice 3:"${#a[@]}" "${a[@]}")
>>> echo $(array.slice 3: "${a[@]}")
3 4 5
3 4 5
>>> local a=(0 1 2 3 4 5)
>>> # from the second-last (included) to the end
>>> echo $(array.slice -2:"${#a[@]}" "${a[@]}")
>>> echo $(array.slice -2: "${a[@]}")
4 5
4 5
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice -4:-2 "${a[@]}")
2 3

If no range is given, it works like normal array indices.

>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice -1 "${a[@]}")
5
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice -2 "${a[@]}")
4
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice 0 "${a[@]}")
0
>>> local a=(0 1 2 3 4 5)
>>> echo $(array.slice 1 "${a[@]}")
1
>>> local a=(0 1 2 3 4 5)
>>> array.slice 6 "${a[@]}"; echo $?
1
>>> local a=(0 1 2 3 4 5)
>>> array.slice -7 "${a[@]}"; echo $?
1

Module change_root

Function change_root

This function performs a linux change root if needed and provides all kernel api filesystems in target root by using a change root interface with minimal needed rights.

Example:

change_root /new_root /usr/bin/env bash some arguments

Function change_root_with_fake_fallback

Perform the available change root program wich needs at least rights.

Example:

change_root_with_fake_fallback /new_root /usr/bin/env bash some arguments

Function change_root_with_kernel_api

Performs a change root by mounting needed host locations in change root environment.

Example:

change_root_with_kernel_api /new_root /usr/bin/env bash some arguments

Module core

Function core_get_all_aliases

Returns all defined aliases in the current scope.

Function core_get_all_declared_names

Return all declared variables and function in the current scope.

E.g. declarations="$(core.get_all_declared_names)"

Function core_import

IMPORTANT: Do not use core.import inside functions -> aliases do not work TODO: explain this in more detail

>>> (
>>> core.import logging
>>> logging_set_level warn
>>> core.import test/mockup_module-b.sh false
>>> )
+doc_test_contains
imported module c
module "mockup_module_c" defines unprefixed name: "foo123"
imported module b

Modules should be imported only once.

>>> (core.import test/mockup_module_a.sh && \
>>>     core.import test/mockup_module_a.sh)
imported module a
>>> (
>>> core.import test/mockup_module_a.sh false
>>> echo $core_declared_functions_after_import
>>> )
imported module a
mockup_module_a_foo
>>> (
>>> core.import logging
>>> logging_set_level warn
>>> core.import test/mockup_module_c.sh false
>>> echo $core_declared_functions_after_import
>>> )
+doc_test_contains
imported module b
imported module c
module "mockup_module_c" defines unprefixed name: "foo123"
foo123

Function core_is_defined

Tests if variable is defined (can also be empty)

>>> local foo="bar"
>>> core_is_defined foo; echo $?
>>> [[ -v foo ]]; echo $?
0
0
>>> local defined_but_empty=""
>>> core_is_defined defined_but_empty; echo $?
0
>>> core_is_defined undefined_variable; echo $?
1
>>> set -o nounset
>>> core_is_defined undefined_variable; echo $?
1

Same Tests for bash < 4.2

>>> core__bash_version_test=true
>>> local foo="bar"
>>> core_is_defined foo; echo $?
0
>>> core__bash_version_test=true
>>> local defined_but_empty=""
>>> core_is_defined defined_but_empty; echo $?
0
>>> core__bash_version_test=true
>>> core_is_defined undefined_variable; echo $?
1
>>> core__bash_version_test=true
>>> set -o nounset
>>> core_is_defined undefined_variable; echo $?
1

Function core_is_empty

Tests if variable is empty (undefined variables are not empty)

>>> local foo="bar"
>>> core_is_empty foo; echo $?
1
>>> local defined_and_empty=""
>>> core_is_empty defined_and_empty; echo $?
0
>>> core_is_empty undefined_variable; echo $?
1
>>> set -u
>>> core_is_empty undefined_variable; echo $?
1

Function core_is_main

Returns true if current script is being executed.

>>> # Note: this test passes because is_main is called by doc_test.sh which
>>> # is being executed.
>>> core.is_main && echo yes
yes

Function core_rel_path

Computes relative path from $1 to $2. Taken from http://stackoverflow.com/a/12498485/2972353

>>> core_rel_path "/A/B/C" "/A"
../..
>>> core_rel_path "/A/B/C" "/A/B"
..
>>> core_rel_path "/A/B/C" "/A/B/C/D"
D
>>> core_rel_path "/A/B/C" "/A/B/C/D/E"
D/E
>>> core_rel_path "/A/B/C" "/A/B/D"
../D
>>> core_rel_path "/A/B/C" "/A/B/D/E"
../D/E
>>> core_rel_path "/A/B/C" "/A/D"
../../D
>>> core_rel_path "/A/B/C" "/A/D/E"
../../D/E
>>> core_rel_path "/A/B/C" "/D/E/F"
../../../D/E/F
>>> core_rel_path "/" "/"
.
>>> core_rel_path "/A/B/C" "/A/B/C"
.
>>> core_rel_path "/A/B/C" "/"
../../../

Function core_source_with_namespace_check

Sources a script and checks variable definitions before and after sourcing.

Function core_unique

>>> local foo="a
b
a
b
c
b
c"
>>> echo -e "$foo" | core.unique
a
b
c

Module dictionary

Function dictionary_get

Usage: variable=$(dictionary.get dictionary_name key)

Examples

>>> dictionary_get unset_map unset_value; echo $?
1
>>> dictionary__bash_version_test=true
>>> dictionary_get unset_map unset_value; echo $?
1
>>> dictionary_set map foo 2
>>> dictionary_set map bar 1
>>> dictionary_get map foo
>>> dictionary_get map bar
2
1
>>> dictionary_set map foo "a b c"
>>> dictionary_get map foo
a b c
>>> dictionary__bash_version_test=true
>>> dictionary_set map foo 2
>>> dictionary_get map foo
2
>>> dictionary__bash_version_test=true
>>> dictionary_set map foo "a b c"
>>> dictionary_get map foo
a b c

Function dictionary_get_keys

>>> dictionary_set map foo "a b c" bar 5
>>> dictionary_get_keys map
bar
foo
>>> dictionary__bash_version_test=true
>>> dictionary_set map foo "a b c" bar 5
>>> dictionary_get_keys map | sort -u
bar
foo

Function dictionary_set

Usage: dictionary.set dictionary_name key value

Tests

>>> dictionary_set map foo 2
>>> echo ${dictionary__store_map[foo]}
2
>>> dictionary_set map foo "a b c" bar 5
>>> echo ${dictionary__store_map[foo]}
>>> echo ${dictionary__store_map[bar]}
a b c
5
>>> dictionary_set map foo "a b c" bar; echo $?
1
>>> dictionary__bash_version_test=true
>>> dictionary_set map foo 2
>>> echo $dictionary__store_map_foo
2
>>> dictionary__bash_version_test=true
>>> dictionary_set map foo "a b c"
>>> echo $dictionary__store_map_foo
a b c

Module doc_test

The doc_test module implements function and module level testing via "doc strings".

Tests can be run by invoking doc_test.sh file1 folder1 file2 ....

Options:

--help|-h                   Print help message.
--side-by-side              Print diff of failing tests side by side.
--no-check-namespace        Do not warn about unprefixed definitions.
--no-check-undocumented     Do not warn about undocumented functions.
--use-nounset               Accessing undefined variables produces error.
--verbose|-v                Be more verbose

Example output ./doc_test.sh -v arguments.sh

[verbose:doc_test.sh:330] arguments:[PASS]
[verbose:doc_test.sh:330] arguments_get_flag:[PASS]
[verbose:doc_test.sh:330] arguments_get_keyword:[PASS]
[verbose:doc_test.sh:330] arguments_get_parameter:[PASS]
[verbose:doc_test.sh:330] arguments_get_positional:[PASS]
[verbose:doc_test.sh:330] arguments_set:[PASS]
[info:doc_test.sh:590] arguments - passed 6/6 tests in 918 ms
[info:doc_test.sh:643] Total: passed 1/1 modules in 941 ms

A doc string can be defined for a function by defining a variable named __doc__ at the function scope. On the module level, the variable name should be <module_name>__doc__ (e.g. arguments__doc__ for the example above). Note: The doc string needs to be defined with single quotes.

Code contained in a module level variable named <module_name>__doc_test_setup__ will be run once before all the Tests of a module are run. This is usefull for defining mockup functions/data that can be used throughout all tests.

Tests

Tests are delimited by blank lines:

>>> echo bar
bar
>>> echo $(( 1 + 2 ))
3

But can also occur right after another:

>>> echo foo
foo
>>> echo bar
bar

Single quotes can be escaped like so:

>>> echo '$foos'
$foos

Or so

>>> echo '$foos'
$foos

Some text in between.

Multiline output

>>> local i
>>> for i in 1 2; do
>>>     echo $i;
>>> done
1
2

Ellipsis support

>>> local i
>>> for i in 1 2 3 4 5; do
>>>     echo $i;
>>> done
+doc_test_ellipsis
1
2
...

Ellipsis are non greedy

>>> local i
>>> for i in 1 2 3 4 5; do
>>>     echo $i;
>>> done
+doc_test_ellipsis
1
...
4
5

Each testcase has its own scope:

>>> local testing="foo"; echo $testing
foo
>>> [ -z "${testing:-}" ] && echo empty
empty

Syntax error in testcode:

>>> f() {a}
+doc_test_contains
+doc_test_ellipsis
syntax error near unexpected token `{a}
...

Function doc_test_compare_result

>>> local buffer="line 1
>>> line 2"
>>> local got="line 1
>>> line 2"
>>> doc_test_compare_result "$buffer" "$got"; echo $?
0
>>> local buffer="line 1
>>> foo"
>>> local got="line 1
>>> line 2"
>>> doc_test_compare_result "$buffer" "$got"; echo $?
1
>>> local buffer="+doc_test_contains
>>> line
>>> line"
>>> local got="line 1
>>> line 2"
>>> doc_test_compare_result "$buffer" "$got"; echo $?
0
>>> local buffer="+doc_test_contains
>>> line
>>> foo"
>>> local got="line 1
>>> line 2"
>>> doc_test_compare_result "$buffer" "$got"; echo $?
1
>>> local buffer="+doc_test_ellipsis
>>> line
>>> ...
>>> "
>>> local got="line
>>> line 2
>>> "
>>> doc_test_compare_result "$buffer" "$got"; echo $?
0
>>> local buffer="+doc_test_ellipsis
>>> line
>>> ...
>>> line 2
>>> "
>>> local got="line
>>> ignore
>>> ignore
>>> line 2
>>> "
>>> doc_test_compare_result "$buffer" "$got"; echo $?
0
>>> local buffer="+doc_test_ellipsis
>>> line
>>> ...
>>> line 2
>>> "
>>> local got="line
>>> ignore
>>> ignore
>>> line 2
>>> line 3
>>> "
>>> doc_test_compare_result "$buffer" "$got"; echo $?
1

Function doc_test_eval

>>> local test_buffer="
>>> echo foo
>>> echo bar
>>> "
>>> local output_buffer="foo
>>> bar"
>>> doc_test_use_side_by_side_output=false
>>> doc_test_module_under_test=core
>>> doc_test_nounset=false
>>> doc_test_eval "$test_buffer" "$output_buffer"

Function doc_test_parse_args

Function doc_test_parse_doc_string

>>> local doc_string="
>>>     (test)block
>>>     output block
>>> "
>>> _() {
>>>     local output_buffer="$2"
>>>     echo block:
>>>     while read -r line; do
>>>         if [ -z "$line" ]; then
>>>             echo "empty_line"
>>>         else
>>>             echo "$line"
>>>         fi
>>>     done <<< "$output_buffer"
>>> }
>>> doc_test_parse_doc_string "$doc_string" _ "(test)"
block:
output block
>>> local doc_string="
>>>     Some text (block 1).
>>>
>>>
>>>     Some more text (block 1).
>>>     (test)block 2
>>>     (test)block 2.2
>>>     output block 2
>>>     (test)block 3
>>>     output block 3
>>>
>>>     Even more text (block 4).
>>> "
>>> local i=0
>>> _() {
>>>     local test_buffer="$1"
>>>     local output_buffer="$2"
>>>     local text_buffer="$3"
>>>     local line
>>>     (( i++ ))
>>>     echo "text_buffer (block $i):"
>>>     if [ ! -z "$text_buffer" ]; then
>>>         while read -r line; do
>>>             if [ -z "$line" ]; then
>>>                 echo "empty_line"
>>>             else
>>>                 echo "$line"
>>>             fi
>>>         done <<< "$text_buffer"
>>>     fi
>>>     echo "test_buffer (block $i):"
>>>     [ ! -z "$test_buffer" ] && echo "$test_buffer"
>>>     echo "output_buffer (block $i):"
>>>     [ ! -z "$output_buffer" ] && echo "$output_buffer"
>>>     return 0
>>> }
>>> doc_test_parse_doc_string "$doc_string" _ "(test)"
text_buffer (block 1):
Some text (block 1).
empty_line
empty_line
Some more text (block 1).
test_buffer (block 1):
output_buffer (block 1):
text_buffer (block 2):
test_buffer (block 2):
block 2
block 2.2
output_buffer (block 2):
output block 2
text_buffer (block 3):
test_buffer (block 3):
block 3
output_buffer (block 3):
output block 3
text_buffer (block 4):
Even more text (block 4).
test_buffer (block 4):
output_buffer (block 4):

Module documentation

Function documentation_serve

Serves a readme via webserver. Uses Flatdoc.

>>> # TODO write test
>>> echo hans
hans

Module exceptions

NOTE: The try block is executed in a subshell, so no outer variables can be assigned.

>>> exceptions.activate
>>> false
+doc_test_ellipsis
Traceback (most recent call first):
...
>>> exceptions_activate
>>> exceptions.try {
>>>     false
>>> }; exceptions.catch {
>>>     echo caught
>>> }
caught

Exceptions in a subshell:

>>> exceptions_activate
>>> ( false )
+doc_test_ellipsis
Traceback (most recent call first):
...
Traceback (most recent call first):
...
>>> exceptions_activate
>>> exceptions.try {
>>>     (false; echo "this should not be printed")
>>>     echo "this should not be printed"
>>> }; exceptions.catch {
>>>     echo caught
>>> }
+doc_test_ellipsis
caught

Nested exceptions:

>>> exceptions_foo() {
>>>     true
>>>     exceptions.try {
>>>         false
>>>     }; exceptions.catch {
>>>         echo caught inside foo
>>>     }
>>>     false # this is cought at top level
>>>     echo this should never be printed
>>> }
>>>
>>> exceptions.try {
>>>     exceptions_foo
>>> }; exceptions.catch {
>>>     echo caught
>>> }
>>>
caught inside foo
caught

Exceptions are implicitely active inside try blocks:

>>> foo() {
>>>     echo $1
>>>     true
>>>     exceptions.try {
>>>         false
>>>     }; exceptions.catch {
>>>         echo caught inside foo
>>>     }
>>>     false # this is not caught
>>>     echo this should never be printed
>>> }
>>>
>>> foo "EXCEPTIONS NOT ACTIVE:"
>>> exceptions_activate
>>> foo "EXCEPTIONS ACTIVE:"
+doc_test_ellipsis
EXCEPTIONS NOT ACTIVE:
caught inside foo
this should never be printed
EXCEPTIONS ACTIVE:
caught inside foo
Traceback (most recent call first):
...

Exceptions inside conditionals:

>>> exceptions_activate
>>> false && echo "should not be printed"
>>> (false) && echo "should not be printed"
>>> exceptions.try {
>>>     (
>>>     false
>>>     echo "should not be printed"
>>>     )
>>> }; exceptions.catch {
>>>     echo caught
>>> }
caught

Print a caught exception traceback.

>>> exceptions.try {
>>>     false
>>> }; exceptions.catch {
>>>     echo caught
>>>     echo "$exceptions_last_traceback"
>>> }
+doc_test_ellipsis
caught
Traceback (most recent call first):
...

Different syntax variations are possible.

>>> exceptions.try {
>>>     ! true
>>> }; exceptions.catch {
>>>     echo caught
>>> }
>>> exceptions.try
>>>     false
>>> exceptions.catch {
>>>     echo caught
>>> }
caught
>>> exceptions.try
>>>     false
>>> exceptions.catch
>>>     echo caught
caught
>>> exceptions.try {
>>>     false
>>> }
>>> exceptions.catch {
>>>     echo caught
>>> }
caught
>>> exceptions.try {
>>>     false
>>> }
>>> exceptions.catch
>>> {
>>>     echo caught
>>> }
caught

Function exceptions_deactivate

>>> set -o errtrace
>>> trap 'echo $foo' ERR
>>> exceptions.activate
>>> trap -p ERR | cut --delimiter "'" --fields 2
>>> exceptions.deactivate
>>> trap -p ERR | cut --delimiter "'" --fields 2
exceptions_error_handler
echo $foo

Module logging

The available log levels are: error critical warn info debug

The standard loglevel is critical

>>> logging.get_level
>>> logging.get_commands_level
critical
critical
>>> logging.error error-message
>>> logging.critical critical-message
>>> logging.warn warn-message
>>> logging.info info-message
>>> logging.debug debug-message
+doc_test_contains
error-message
critical-message

If the output of commands should be printed, the commands_level needs to be greater than or equal to the log_level.

>>> logging.set_level critical
>>> logging.set_commands_level debug
>>> echo foo
>>> logging.set_level info
>>> logging.set_commands_level info
>>> echo foo
foo

Another logging prefix can be set by overriding "logging_get_prefix".

>>> logging_get_prefix() {
>>>     local level=$1
>>>     echo "[myprefix - ${level}]"
>>> }
>>> logging.critical foo
[myprefix - critical] foo

"logging.plain" can be used to print at any log level and without the prefix.

>>> logging.set_level critical
>>> logging.set_commands_level debug
>>> logging.plain foo
foo

"logging.cat" can be used to print files (e.g "logging.cat < file.txt") or heredocs. Like "logging.plain", it also prints at any log level and without the prefix.

>>> echo foo | logging.cat
foo

Function logging_plain

>>> logging.set_level info
>>> logging.set_commands_level debug
>>> logging.debug "not shown"
>>> echo "not shown"
>>> logging.plain "shown"
shown

Function logging_set_file_descriptors

>>> local test_file="$(mktemp)"
>>> logging.plain "test_file:" >"$test_file"
>>> logging_set_file_descriptors ""
>>> logging.cat "$test_file"
>>> rm "$test_file"
test_file:
>>> local test_file="$(mktemp)"
>>> logging_set_file_descriptors "$test_file"
>>> logging_set_file_descriptors ""
>>> echo "test_file:" >"$test_file"
>>> logging.cat "$test_file"
>>> rm "$test_file"
test_file:
>>> local test_file="$(mktemp)"
>>> logging.plain "test_file:" >"$test_file"
>>> logging_set_file_descriptors "$test_file" --logging=tee
>>> logging.plain foo
>>> logging_set_file_descriptors ""
>>> logging.cat "$test_file"
>>> rm "$test_file"
foo
test_file:
foo
>>> local test_file="$(mktemp)"
>>> logging.plain "test_file:" >"$test_file"
>>> logging_set_file_descriptors "$test_file" --logging=off --commands=file
>>> logging.plain not shown
>>> echo foo
>>> logging_set_file_descriptors ""
>>> logging.cat "$test_file"
>>> rm "$test_file"
test_file:
foo
>>> local test_file="$(mktemp)"
>>> logging.plain "test_file:" >"$test_file"
>>> logging_set_file_descriptors "$test_file" --logging=off
>>> logging.plain not shown
>>> echo foo
>>> logging_set_file_descriptors ""
>>> logging.cat "$test_file"
>>> rm "$test_file"
foo
test_file:
>>> local test_file="$(mktemp)"
>>> logging.plain "test_file:" >"$test_file"
>>> logging_set_file_descriptors "$test_file" --commands=tee
>>> logging.plain logging
>>> echo echo
>>> logging_set_file_descriptors ""
>>> logging.cat "$test_file"
>>> rm "$test_file"
logging
echo
test_file:
echo
>>> local test_file="$(mktemp)"
>>> logging.plain "test_file:" >"$test_file"
>>> logging_set_file_descriptors "$test_file" --commands=file
>>> logging.plain logging
>>> echo echo
>>> logging_set_file_descriptors ""
>>> logging.cat "$test_file"
>>> rm "$test_file"
logging
test_file:
echo
>>> local test_file="$(mktemp)"
>>> logging.plain "test_file:" >"$test_file"
>>> logging_set_file_descriptors "$test_file" --logging=file --commands=file
>>> logging.plain logging
>>> echo echo
>>> logging_set_file_descriptors ""
>>> logging.cat "$test_file"
>>> rm "$test_file"
test_file:
logging
echo
>>> local test_file="$(mktemp)"
>>> logging.plain "test_file:" >"$test_file"
>>> logging_set_file_descriptors "$test_file" --logging=file --commands=file
>>> logging.plain logging
>>> echo echo
>>> logging_set_file_descriptors ""
>>> logging.cat "$test_file"
>>> rm "$test_file"
test_file:
logging
echo
>>> local test_file="$(mktemp)"
>>> logging.plain "test_file:" >"$test_file"
>>> logging_set_file_descriptors "$test_file" --logging=file --commands=tee
>>> logging.plain logging
>>> echo echo
>>> logging_set_file_descriptors ""
>>> logging.cat "$test_file"
>>> rm "$test_file"
echo
test_file:
logging
echo
>>> local test_file="$(mktemp)"
>>> logging.plain "test_file:" >"$test_file"
>>> logging_set_file_descriptors "$test_file" --logging=file --commands=off
>>> logging.plain logging
>>> echo echo
>>> logging_set_file_descriptors ""
>>> logging.cat "$test_file"
>>> rm "$test_file"
test_file:
logging
>>> local test_file="$(mktemp)"
>>> logging.plain "test_file:" >"$test_file"
>>> logging_set_file_descriptors "$test_file" --logging=tee --commands=tee
>>> logging.plain logging
>>> echo echo
>>> logging_set_file_descriptors ""
>>> logging.cat "$test_file"
>>> rm "$test_file"
logging
echo
test_file:
logging
echo

Test exit handler

>>> local test_file fifo
>>> test_file="$(mktemp)"
>>> fifo=$(logging_set_file_descriptors "$test_file" --commands=tee; \
>>>    echo $logging_tee_fifo)
>>> [ -p "$fifo" ] || echo fifo deleted
>>> rm "$test_file"
fifo deleted

Function logging_set_level

>>> logging.set_commands_level info
>>> logging.set_level info
>>> echo $logging_level
>>> echo $logging_commands_level
3
3

Function logging_set_log_file

>>> local test_file="$(mktemp)"
>>> logging.plain "test_file:" >"$test_file"
>>> logging.set_log_file "$test_file"
>>> logging.plain logging
>>> logging.set_log_file "$test_file"
>>> echo echo
>>> logging.set_log_file ""
>>> logging.cat "$test_file"
>>> rm "$test_file"
logging
echo
test_file:
logging
echo
>>> logging.set_commands_level debug
>>> logging.set_level debug
>>> local test_file="$(mktemp)"
>>> logging.plain "test_file:" >"$test_file"
>>> logging.set_log_file "$test_file"
>>> logging.plain 1
>>> logging.set_log_file ""
>>> logging.set_log_file "$test_file"
>>> logging.plain 2
>>> logging.set_log_file ""
>>> logging.cat "$test_file"
>>> rm "$test_file"
1
2
test_file:
1
2

Module time

Module ui

This module provides variables for printing colorful and unicode glyphs. The Terminal features are detected automatically but can also be enabled/disabled manually (see ui.enable_color and ui.enable_unicode_glyphs).

Function ui_disable_color

Disables color output explicitly.

>>> ui.enable_color
>>> ui.disable_color
>>> echo -E "$ui_color_red" red "$ui_color_default"
red

Function ui_disable_unicode_glyphs

Disables unicode glyphs explicitly.

>>> ui.enable_unicode_glyphs
>>> ui.disable_unicode_glyphs
>>> echo -E "$ui_powerline_ok"
+

Function ui_enable_color

Enables color output explicitly.

>>> ui.disable_color
>>> ui.enable_color
>>> echo -E $ui_color_red red $ui_color_default
�[0;31m red �[0m

Function ui_enable_unicode_glyphs

Enables unicode glyphs explicitly.

>>> ui.disable_unicode_glyphs
>>> ui.enable_unicode_glyphs
>>> echo -E "$ui_powerline_ok"
✔

Module utils

Function utils_dependency_check

This function check if all given dependencies are present.

Example:

>>> utils_dependency_check mkdir ls; echo $?
0
>>> utils_dependency_check mkdir __not_existing__ 1>/dev/null; echo $?
2
>>> utils_dependency_check __not_existing__ 1>/dev/null; echo $?
2
>>> utils_dependency_check "ls __not_existing__"; echo $?
__not_existing__
2

Function utils_dependency_check_pkgconfig

This function check if all given libraries can be found.

Example:

>>> utils_dependency_check_shared_library libc.so; echo $?
0
>>> utils_dependency_check_shared_library libc.so __not_existing__ 1>/dev/null; echo $?
2
>>> utils_dependency_check_shared_library __not_existing__ 1>/dev/null; echo $?
2

Function utils_dependency_check_shared_library

This function check if all given shared libraries can be found.

Example:

>>> utils_dependency_check_shared_library libc.so; echo $?
0
>>> utils_dependency_check_shared_library libc.so __not_existing__ 1>/dev/null; echo $?
2
>>> utils_dependency_check_shared_library __not_existing__ 1>/dev/null; echo $?
2

Function utils_find_block_device

>>> utils_find_block_device "boot_partition"
/dev/sdb1
>>> utils_find_block_device "boot_partition" /dev/sda
/dev/sda2
>>> utils_find_block_device "discoverable by blkid"
/dev/sda2
>>> utils_find_block_device "_partition"
/dev/sdb1 /dev/sdb2
>>> utils_find_block_device "not matching anything" || echo not found
not found
>>> utils_find_block_device "" || echo not found
not found