Functional Bash (FB for short) describes a way of writing Bash scripts that are functional in terms of functioning properly and also being implemented in some kind of functional style.
Each function of such a script consists of only a few lines - mostly calling other functions. Instead of repeating standard implementations like loops again and again, these implementations are abstracted by FB helper functions.
Global script (environment) variables are defined in only one function at the beginning of the script, which also sets them to read only after definition.
An “ordinary” function may use these variables (without changing them). If a function needs (changeable) variables, they have to be defined as local.
Clone functional_bash
to an arbitrary directory, e.g. to
~/repositories
:
mkdir -p ~/repositories && cd $_
git clone https://gitlab.com/w6g/functional_bash
Go to directory functional_bash
and copy
lib/functional_bash.sh
to /usr/local/lib
and
adjust ownership and access rights:
cd functional_bash
sudo cp lib/functional_bash.sh /usr/local/lib
sudo chown root:root /usr/local/lib/functional_bash.sh
sudo chmod 644 /usr/local/lib/functional_bash.sh
Source functional_bash.sh at the
top of your Bash script. See also the Bash scripts in
bin
:
You may call a function as follows:
func "arg1" "arg2" "arg3" ...
Inside the function, the arguments are stored in the special array
variable $@
; access to the array members is possible via
$1
, $2
, …, $n
.
You may also store the arguments in an array variable and call the function with this array variable.
array=( "arg1" "arg2" "arg3" )
func "${array[@]}"
A function may also get input from its stdin. More than one input line is allowed.
func <<< "Hello"
func < "/etc/passwd"
echo "Hello" | func1 | func2
A function outputs its result to stdout, where more than one output line is allowed.
func <<< "Hello"
echo "Hello" | func1 | func2
A function may return a numerical value, where 0 means true, and any value unequal to 0 means false.
f1() { [ -z "$var" ]; } # f1 returns 0 (true), if 'var' is empty
f2() { true; } # f2 always returns 0 (true)
f3() { false; } # f3 always returns a value != 0 (false)
An FB script should not use other values than 0 (true) and != 0 (false).
We already heard about array variables like this:
my_array=("a" "b" "c")
. A sequence of strings, separated
with space, like "a" "b" "c"
, is also called
array, when this sequence is used as function arguments:
my_array=("a" "b" "c") # 'my_array' is an array (variable)
func "a" "b" "c" # 'func' is called with the array '"a" "b" "c"'
The sequence of lines that can be forwarded to a function or that a function may output is called a list.
You may also store a list in an ordinary variable and “feed” stdin of a function with it:
my_list="$(cat "LICENSE")"
func <<< "$my_list"
There is nothing FB-special with lists. The name is just a convention.
It is not always handy that a function may only output a sequence of lines as its result. Sometimes it is useful to split one line into fields. Such a split line is called a tuple.
A tuple consists of fields, where the fields are separated by ‘,’ (comma). To prevent problems, if a field contains a comma, and to allow even nested tuples, the fields are base64-encoded under the hood.
A special variant of a tuple with two components is called a pair.
Tuples (and the related helper functions) are FB-specific.
show_list output a list in a human readable way
list convert an array to a list
llength output the number of list elements
ltake output the first n list elements
ldrop drop the first n list elements and output the rest
lhead output the first list element
ltail output all list elements except the first
llast output the last list element
linit output all list elements except the last
lcons add one value to the beginning of a list
lprepend add an array to the beginning of a list
lappend add an array to the end of a list
lreverse reverse the element order of a list
lminval output the minimum value in a list
lmaxval output the maximum value in a list
lminlen output the minimum string length in a list
lmaxlen output the maximum string length in a list
list_to_array store all list elements in an array variable
to_array store all function arguments in an array variable
lmap map a function over all elements of a list
lapply map an array of functions over all elements of a list
lbind same as lmap
lfilter filter a list with a function
lfilter_nonhash remove empty and '#' elements in a list
ljoin_backslash_terminated
join lines with 'backslash' 'linefeed' in between
lfoldl fold a list with an accumulator and a function
lscanl same as lfoldl, but output each accumulator value
lfoldm same as lfoldl, but let the function create outputs
lunfoldr unfold a seed and a function to a list
show_tuple output an n-tuple in a human readable way
to_pair create a pair from function arguments
list_to_tuple convert a list to an n-tuple
to_tuple create an n-tuple from function arguments
split_at split a string to a pair at a given delimiter
tnth output the n-th component of an n-tuple
tfst output the first component of a pair
tsnd output the second component of a pair
tthd output the third component of a triple
swap_pair swap the components of a pair
tmap map a function over all components of an n-tuple
tuple_to_list convert an n-tuple to a list
get_unset ouput the value for 'unset'
get_set output the value for 'set'
is_unset checks if a given value is 'unset'
is_set checks if a given value is 'set'
get_minint output the minimum int value
get_maxint output the maximum int value
get_min output the minimum of two values
get_max output the maximum of two values
strtrim remove leading and trailing spaces in a string
is_subset check if an array contains (also) all elements of a list
intersection ouput a list with all elements both in an array and a list
amap map a function over all keys/values of an associative array
split_to_array split a string to an array at a given delimiter
zipwith combine two arrays to one with a function
get_script_name output the name of the current script
put_cond output first or second value with third value as condition
put_err output an error message on stderr
put_msg output a message on stdout
put_value output a value on stdout or a message on stderr
align_with output a dedicated number of alignment characters
put_aligned output two aligned strings
put_if_different output a string, if it differs from predecessor
is_installed check if a given tool is installed
are_installed check if all tools, given as list, are installed
is_reabable check if a file with a given name is readable
read_file read file content or stdin as list
get_options handle command line options and arguments
get_arg output a dedicated command line argument or its default
get_conf get a configuration from a list
read_conf_file read a configuration from an optional file
set_conf_env set environment from configuration
show_list
- output a list in a human readable wayTakes a list and outputs it on stdout in a readable way, that is, with brackets around it and commas between the values.
stdin: list
stdout: readable list
returns: true
Example:
echo -e "a\nb\nc\n" | show_list # [a,b,c]
list
- convert
an array to a listTakes an array, converts it to a list, and outputs the result on stdout.
$1...: array
stdout: list
returns: true
Examples:
list "a" "b" "c" | show_list # [a,b,c]
array=( 1 2 3 ) ; list "${array[@]}" | show_list # [1,2,3]
list_to_array
- store all list elements in an array variableTakes a list and an array nameref as function argument, puts all lines into the array, referenced by the nameref, and returns true.
stdin: list
$1: array nameref
returns: true
Example:
list_to_array array < <(list "X" "Y" "Z")
list "${array[@]}" | show_list" # [X,Y,Z]
Watch that list "X" "Y" "Z" | list_to_array array
does
not work, because the second part of the pipe is executed in a subshell,
where the content of the nameref array
is gone after
finish.
to_array
- store all function arguments in an array variableTakes an array nameref and a variable number of values as function arguments, puts all values into the array, referenced by the nameref, and returns true.
$1: array nameref
$2...: values, to be stored in array
returns: true
Example:
to_array array "X" "Y" "Z"
list "${array[@]}" | show_list" # [X,Y,Z]
lmap
- map a function over all elements of a listTakes a list and a variable number of function arguments, where the
first argument is a function name. The remaining arguments are forwarded
to this function. lmap
calls the function consecutively
with all list elements, where the current list element is the last
function argument. lmap
returns false, if one of
the function calls returns false, otherwise true.
stdin: list
$1: function name
$2...: optional function arguments
stdout: mapped list
returns: true or false, depending on function return values
Example:
func() { echo "$1<$2>"; }
list "a" "b" "c" | show_list # [a,b,c]
list "a" "b" "c" | lmap func "^" | show_list # [^<a>,^<b>,^<c>]
lmap
, together with the list, mimics the behavior of a
functor, if the function, given by the first argument, outputs
one line on stdout. In this case, the structure of the input
list is not changed, but each element of the input list is mapped to a
related element of the output list.
lbind
- same as
lmap
lbind
, together with the list, mimics the behavior of a
monad, if the function, given by the first argument, outputs
more than one line on stdout. In this case, each element of the
input list is mapped to a related list, which is one element of the
output list.
After that, the output list is “flattened” from list of
lists to just list. Since there is in fact no list of
lists, lbind
does the same as lmap
does.
So, the difference of both functions is academic here.
lfoldl
- fold a list with an accumulator and a functionTakes a list and a variable number of function arguments, where the
first argument is an (initial) accumulator value and the second argument
is a function name. The remaining arguments are forwarded to this
function. lfoldl
calls the function consecutively with the
accumulator value and all list elements (starting with the most-left
element), where the current list element is the last function
argument.
The called function is expected to output the (possibly modified)
accumulator value on stdout. lfoldl
calls the function with
this new accumulator value and the next list element.
lfoldl
outputs the final accumulator value on
stdout and returns false, if one of the function calls
returns false, otherwise true.
stdin: list
$1: initial accumulator value
$2: function name
$3...: optional function arguments
stdout: final accumulator value
returns: true or false, depending on function return values
Example:
func() { (( ($2 % 2) == 0 )); put_cond "$(($1 + $2))" "$1" $?; }
list 1 1 2 3 4 6 # [1,1,2,3,4,6]
list 1 1 2 3 4 6 | lfoldl 0 func # 5 (sum of all odd numbers in list)
amap
- map a function over all keys/values of an associative arrayTakes a nameref of an associative array and a variable number of
remaining function arguments, where the first argument is a function
name. The remaining arguments are forwarded to this function.
amap
calls the function consecutively with all keys and
values of the associative array. amap
returns
false, if one of the function calls returns false,
otherwise true.
$1: nameref of associative array
$2: function name
$3...: optional function arguments
stdout: list of mapped associative array
returns: true or false, depending on function return values
Example:
declare -A assoc=( ["a"]="A" ["b"]="B" ["c"]="C" )
func() { echo "key=$1/value=$2"; }
amap assoc func | sort | show_list # [key=a/value=A,key=b/value=B,key=c/value=C]
get_script_name
- output the name of the current scriptTakes the environment variable script name
and outputs
its content on stdout. If script_name
is not set,
the function outputs "$(basename "$0")"
instead. The
function always returns true.
put_err
-
output an error message on stderrTakes an (error) message as function argument, outputs
"$(get_script_name): ${1}!"
on stderr, and returns
false.
put_msg
-
output a message on stdoutTakes a message as function argument, outputs
"$(get_script_name): ${1}"
on stdout, and returns
true.
put_value
- output a value on stdout or an error message on
stderrTakes a (string) value and an error message as function arguments. If
the value is non-empty, the function outputs the value on
stdout and returns true. If the value is empty, the
function calls output_err
with the error message and
returns false.
put_cond
- output first or second value with third value as conditionTakes a “left” and a “right” (string) value and a “bool” value (where 0 is treated as true as usual) as function arguments. If the “bool” value is false, the function outputs the “left” value on stdout. If the “bool” value is true, the function outputs the “right” value on stdout. The function always returns true.
align_with
- output a dedicated number of alignment charactersTakes an alignment string, a distance value, and a string as function arguments and outputs a number of alignment strings on stdout. This number is the difference of the distance value and the size of the string, given as third function argument. The function always returns true.