| examples | ||
| images | ||
| LICENSE | ||
| README.org | ||
| sf | ||
sf – script framework

script framework can be used to simplify and beautify Bash scripts. It provides:
- Argument parsing
- Usage output
- Input functions
- Output functions
- Text formatting variables
All just by declaring some variables and sourcing it. Or keep your scripts self-contained and include it as an oneliner.
The usage is pretty self-explanatory once you have seen it. If you're curious and don't want to read through the documentation, head directly to the examples.
Here is the oneliner version of sf which was created with this tool:
# sf -- script framework (https://github.com/Deleh/sf)
sftrs=$'\e[0m';sftbf=$'\e[1m';sftdim=$'\e[2m';sftul=$'\e[4m';sftblk=$'\e[5m';sftinv=$'\e[7m';sfthd=$'\e[8m';sftclr=$'\e[1A\e[K';sftk=$'\e[30m';sftr=$'\e[31m';sftg=$'\e[32m';sfty=$'\e[33m';sftb=$'\e[34m';sftm=$'\e[35m';sftc=$'\e[36m';sftw=$'\e[97m';function sferr { echo "${sftbf}${sftr}ERROR${sftrs} $1";[ -z "$2" ]&&exit 1;};function sfwarn { echo "${sftbf}${sfty}WARNING${sftrs} $1";};function sfask { if [ "$2" == "" ];then read -p "$1? [${sftbf}Y${sftrs}/${sftbf}n${sftrs}] " sfin;[[ "$sfin" =~ y|Y|^$ ]]&&sfin=true||sfin=false;else read -p "$1? [${sftbf}y${sftrs}/${sftbf}N${sftrs}] " sfin;[[ "$sfin" =~ n|N|^$ ]]&&sfin=false||sfin=true;fi;};function sfget { [ "$2" != "" ]&&read -p "$1 [${sftbf}$2${sftrs}]: " sfin||read -p "$1: " sfin;[ "$sfin" == "" ]&&[ "$2" != "" ]&&sfin="$2";};function _sferr { echo "${sftbf}${sftr}SF PARSE ERROR${sftrs} $1";exit 1;};OLDIFS=$IFS;IFS=";";_sfphead="";_sfpdesc="";_sfodesc=" -h, --help;Show this help message\n";_sfexamples="";_sfpargs=();declare -A _sfflags;declare -A _sfargs;for a in "${sfargs[@]}";do _sfsubst=${a//";"};_sfcount="$(((${#a} - ${#_sfsubst})))";if [ $_sfcount -eq 1 ];then read -r -a _sfparsearr<<<"${a}";_sfpargs+=("${_sfparsearr[0]}");_sfphead="$_sfphead ${_sfparsearr[0]}";_sfpdesc="$_sfpdesc ${_sfparsearr[0]};${_sfparsearr[1]}\n";elif [ $_sfcount -eq 2 ];then read -r -a _sfparsearr<<<"${a}";_sfflags["-${_sfparsearr[1]}"]="${_sfparsearr[0]}";_sfflags["--${_sfparsearr[0]}"]="${_sfparsearr[0]}";declare ${_sfparsearr[0]}=false;_sfodesc="$_sfodesc -${_sfparsearr[1]}, --${_sfparsearr[0]};${_sfparsearr[2]}\n";elif [ $_sfcount -eq 4 ];then read -r -a _sfparsearr<<<"${a}";_sfargs["-${_sfparsearr[1]}"]="${_sfparsearr[0]}";_sfargs["--${_sfparsearr[0]}"]="${_sfparsearr[0]}";declare ${_sfparsearr[0]}="${_sfparsearr[3]}";_sfodesc="$_sfodesc -${_sfparsearr[1]}, --${_sfparsearr[0]} ${_sfparsearr[2]};${_sfparsearr[4]} (default: ${_sfparsearr[3]})\n";else _sferr "Wrong argument declaration: $a";fi;done;[ "$sfparr" == true ]&&[ "${#_sfpargs[@]}" == 0 ]&&_sferr "At least one positional argument must be used with 'sfparr'";for e in "${sfexamples[@]}";do _sfsubst=${e//";"};_sfcount="$(((${#e} - ${#_sfsubst})))";if [ $_sfcount -eq 1 ];then read -r -a _sfparsearr<<<"${e}";_sfexamples="$_sfexamples ${_sfparsearr[0]};${_sfparsearr[1]}\n";else _sferr "Wrong example declaration: $e";fi;done;IFS=$OLDIFS;function _sfusage { echo -n "Usage: $(basename $0) [OPTIONS]";echo -ne "$_sfphead";[ "$sfparr" == true ]&&echo -n " ...";echo;[ ! -z ${sfdesc+x} ]&&echo -e "\n$sfdesc";if [ "$_sfpdesc" != "" ];then echo -e "\nPOSITIONAL ARGUMENTS";echo -e "$_sfpdesc"|column -c 80 -s ";" -t -W 2;fi;if [ "$_sfodesc" != "" ];then echo -e "\nOPTIONS";echo -e "$_sfodesc"|column -c 80 -s ";" -t -W 2;fi;if [ "$_sfexamples" != "" ];then echo -e "\nEXAMPLES";echo -e "$_sfexamples"|column -c 80 -s ";" -t -W 2;fi;if [ ! -z ${sfextra+x} ];then echo -e "\n$sfextra";fi;exit 0;};for a in "$@";do [ "$a" == "-h" ]||[ "$a" == "--help" ]&&_sfusage;done;while(("$#"));do if [ ! -z ${_sfflags["$1"]} ];then declare ${_sfflags["$1"]}=true;elif [ ! -z ${_sfargs["$1"]} ];then if [ -n "$2" ]&&[ "${2:0:1}" != "-" ];then declare ${_sfargs["$1"]}="$2";shift;else sferr "Argument for '$1' missing";fi;else if [ "${1:0:1}" == "-" ];then sferr "Unsupported argument: $1";else if [ "${#_sfpargs[@]}" != 0 ];then declare ${_sfpargs[0]}="$1";[ "$sfparr" == true ]&&_sfplast="${_sfpargs[0]}"&&_sfparr=("$1");_sfpargs=("${_sfpargs[@]:1}");elif [ "$sfparr" == true ];then _sfparr+=("$1");else sferr "Too many positional arguments";fi;fi;fi;shift;done;[ "$sfparr" == true ]&&[ "${#_sfparr[@]}" -ge 1 ]&&read -r -a ${_sfplast}<<<"${_sfparr[@]}";if [ "$sfparr" != true ]&&[ ${#_sfpargs[@]} != 0 ];then for p in "${_sfpargs[@]}";do sferr "Positional argument '$p' missing" 0;done;exit 1;fi;unset a e _sfargs _sferr _sfexamples _sfflags _sfodesc _sfpargs _sfparr _sfpdesc _sfphead _sfplast _sfusage
Requirements
- At least Bash 4.x
Usage
The general usage for writing a script with sf is:
- Declare sf-variables at the top of your script
- Include sf
- Write your script with already parsed arguments, input functions, output functions and text formatting variables
1. sf-variables
This is the list of variables which can be set before including sf. Everything is optional.
| Name | Description | Example |
|---|---|---|
sfdesc |
Description of the script | sfdesc="This script does nothing." |
sfargs |
Array for declaration of arguments, positional arguments and flags. Look below for more information | See below |
sfparr |
Flag which indicates if the last declared positional argument should be treated as array | sfparr=true |
sfexamples |
Array for declaration of examples for the usage output. Look below for more information | See also below |
sfextra |
Additional usage output | sfextra="No copyright." |
Examples which show the usage of all variables can be found below and in the examples directory.
sfargs
This is an array of strings. Every string defines an argument, a flag or an positional argument of the script. The type is defined by the amount of semicolons in the string.
| Type | Declaration order | Example |
|---|---|---|
| Positional argument | <name>;<description> |
sfargs+=("FILE;File to read") |
| Flag | <name>;<shorthand>;<description> |
sfargs+=("verbose;v;Enable verbose output") |
| Argument | <name>;<shorthand>;<value_name>;<default_value>;<description> |
sfargs+=("text;t;TEXT;done;Print TEXT when finished") |
The order of declaration defines the order in the usage output.
sfexamples
This is also an array of strings.
Examples are of the form <command>;<description> and can be added to sf like this:
sfexamples+=("count 8;Count to eight")
2. Include sf
There are three methods of including sf:
-
Grab the sf file from this repo, place it next to your script and source it:
source "$(dirname $0)/sf" - Copy and paste the oneliner from the top of this README
-
Source sf from the web with
curl:source <(curl -s https://raw.githubusercontent.com/Deleh/sf/master/sf)Note that this adds an online dependency to your script and if sf changes, your script may break. This method should only be used for testing purposes.
3. Write your script
sf deals with missing inputs and handles the parsing of arguments.
This means that after sf was included you can be sure that all variables have assigned values.
Flags are either false or true, arguments have a provided value or the default value and positional arguments have a provided value.
The values are stored in variables with the name $<name>.
If you declared for example a flag like this:
sfargs+=("verbose;v;Enable verbose output")
Then the variable $verbose exists with a value of either false or true.
Input functions
User input can be requested with two functions.
After calling a function, the user input is provided in the variable $sfin.
sfask |
Takes a string as input and asks for yes or no. If an additional argument is provided (doesn't matter what), no will be default. $sfin is either true or false |
sfget |
Takes a string as input and asks for user input. If a second argument is provided, this will be the default if no user input was entered |
Here is a small snippet to show the usage:
sfget "Please enter your name" "John"
echo "Hello $sfin"
sfask "Do you want to proceed"
if [ "$sfin" == true ]; then
sfask "Are you sure" "no"
[ "$sfin" == true ] && echo "Please continue..." || echo "Bye"
else
echo "Bye"
fi
And the execution:
Please enter your name [John]: Jane Hello Jane! Do you want to proceed? [Y/n] Are you sure? [y/N] y Please continue...
Note that the colon and question marks get added by the functions.
Output functions
Two output functions are provided which can be used to throw warnings and errors.
sfwarn |
Takes a string as input and prints a warning |
sferr |
Takes a string as input, prints an error and exits with code 1. If an additional argument is passed (doesn't matter what), it will just throw an error and don't exit |
Text formatting variables
The following text formatting variables can be used to modify the output:
sftrs |
Reset formatting |
sftbf |
Bold |
sftdim |
Dim |
sftul |
Underline |
sftblk |
Blinking |
sftinv |
Invert foreground/background |
sfthd |
Hidden |
sftclr |
Clear the previous line |
sftk |
Black |
sftr |
Red |
sftg |
Green |
sfty |
Yellow |
sftb |
Blue |
sftm |
Magenta |
sftc |
Cyan |
sftw |
White |
The variables can be used directly in echo, no -e needed.
To echo the word "framework" bold and red use the variables for example like this:
echo "${sftbf}${sftr}framework${sftrs}"
Examples
All examples can also be found in the examples directory.
Play around with the sf-variables and see what happens.
Count
This example script counts from/to a number and shows the general usage of sf-variables:
#!/usr/bin/env bash
# ----------------------
# sf -- script framework
# ----------------------
# Declare sf variables
sfdesc="A simple counter."
sfargs+=("N;Number to count")
sfargs+=("reverse;r;Count reverse")
sfargs+=("text;t;TEXT;done;Print TEXT when finished counting")
sfexamples+=("count 8;Count to eight")
sfexamples+=("count -r -t go 3;Count reverse from 3 and print 'go'")
sfextra="No copyright at all."
# Include sf, this could be replaced with a long oneliner
source "$(dirname $0)/sf"
# ----------------------
# Actual script
# ----------------------
if [ "$N" -ge 11 ]; then # Use parsed positional argument
sferr "I can only count to/from 10" # Throw an error and exit
fi
counter="$N" # Use parsed positional argument
echo -n "$sftbf" # Print everyting from here bold
while [ "$counter" -ge 1 ]; do
if [ "$reverse" == true ]; then # Use parsed flag
echo " $counter"
else
echo " $(expr $N - $counter + 1)" # Use parsed positional argument
fi
counter=$(expr $counter - 1)
sleep 1
done
echo -n "$sftrs" # Reset text formatting
echo "$text" # Use parsed argument
The usage output of the counter script is:
Usage: count [OPTIONS] N
A simple counter.
POSITIONAL ARGUMENTS
N Number to count
OPTIONS
-h, --help Show this help message
-r, --reverse Count reverse
-t, --text TEXT Print TEXT when finished counting (default: done)
EXAMPLES
count 8 Count to eight
count -r -t go 3 Count reverse from 3 and print 'go'
No copyright at all.
Add
This script adds numbers and shows the usage of sfparr:
#!/usr/bin/env bash
# ----------------------
# sf -- script framework
# ----------------------
# Declare sf variables
sfdesc="Calculate the sum of multiple numbers."
sfargs+=("NUMBERS;Numbers which will be added")
sfargs+=("verbose;v;Enable verbose output")
sfparr=true # Treat the last declared positional argument as array
# Include sf, this could be replaced with a long oneliner
source "$(dirname $0)/sf"
# ----------------------
# Actual script
# ----------------------
sum=0
for n in "${NUMBERS[@]}"; do # Use parsed positional argument array
if [ "$verbose" == true ]; then # Use parsed flag
echo -n "$sum + $n = "
fi
sum="$(expr $sum + $n)"
if [ "$verbose" == true ]; then # Use parsed flag
echo "$sftbf$sum$sftrs" # Use text formatting variables
fi
done
echo "The sum is: $sftbf$sum$sftrs" # Use text formatting variables
And here is the produced usage:
Usage: add [OPTIONS] NUMBERS ...
Calculate the sum of multiple numbers.
POSITIONAL ARGUMENTS
NUMBERS Numbers which will be added
OPTIONS
-h, --help Show this help message
-v, --verbose Enable verbose output
Greet
This example greets a user and asks for the age. It shows the usage of input functions:
#!/usr/bin/env bash
# ----------------------
# sf -- script framework
# ----------------------
# Declare sf variables
sfdesc="Greet a person."
# Include sf, this could be replaced with a long oneliner
source "$(dirname $0)/sf"
# ----------------------
# Actual script
# ----------------------
sfget "Enter your name" # Get input
echo "Hello ${sfin}!" # Use input
sfask "Do you want to tell me your age" # Ask for YES/no
if [ "$sfin" == true ]; then # Use answer
sfget "Enter your Age" # Get input
sfask "Is $sfin really your age" "no" # Use input and ask for yes/NO
if [ "$sfin" == true ]; then # Use answer
echo "Great!"
else
echo "I knew it!"
fi
fi
The produced usage:
Usage: greet [OPTIONS]
Greet a person.
OPTIONS
-h, --help Show this help message