Even though no POSIX-compatible shell is likely to win any performance awards,
the capabilities of modern shells - especailly bash-4
and above - now allow
significant power and flexibility. However, many shell-scripts remain
afterthoughts, quickly thrown together without any consideration of- or
adherence to- any particular standards. Due to the lack of any widely adopted
standard functions, many scripts either lack even the most basic of error-
handling techniques, or spend much time re-implmenting boiler-plate code. This
library is intended to live in /usr/local/lib
, and either be included in any
scripts which wish to make use of the standardised functions - or even sourced
from ~/.bashrc
in order to speed individual script execution.
stdlib.sh
requires at least bash-2.02
(for [[ ... ]]
in-process
conditional evaluation), and several functions use the =~
regular-expression
match operator introduced in bash-3
. If bash-4
features such as
associative arrays are available, then the environment variable
STDLIB_HAVE_BASH_4
is set on load - see below.
Invoking a script which employs stdlib.sh
with the DEBUG
environment
variable set may produce additional diagnostic output. Explicitly setting
DEBUG=2
will additionally output stdlib.sh
internal diagnostic information.
A template.sh
file is now provided with all of the necessary components
needed to use stdlib.sh
already included, making getting started much simpler
and quicker. The suggested configuration is to edit with vi
or vim
with
set modeline
in ~/.vimrc
, to allow for code-folding and so easier editing.
All scripts should, following the interpreter line at the very top of the file,
include the stdlib.sh
loader code from the top of /usr/local/lib/stdlib.sh
.
stdlib will only execute itself if its functions aren't already initialised.
On load, stdlib.sh
invokes set -u
which causes bash to abort execution if
a run-time attempt is made to address an unbound variable. Because of this,
any third-party scripts should be sourced before stdlib.sh
is loaded -
however, since the unbound-variable checking is perfomed at run-time only, any
functions in sourced scripts may still trigger an unbound variable error on
execution.
Only global variables (in ALLCAPS, by convention) should be defined outside of
any function, and should follow the stdlib.sh
inclusion code. The main-loop
of the script's function should be contained within a main()
function, which
should in turn call other functions as appropriate. All other variables should
be declared 'local' within the function in which they are used. Other useful
declarations are:
-
local -i
: Define an integer variable, which accepts only numbers, defaults to value0
, and will never return a value of true in response totest -z
; -
local -u
: Define an upper-case variable, where any assigned value is automatically converted to upper-case; -
local -l
: Define a lower-case variable, where any assigned value is automatically converted to lower-case; -
local -a
: Define an array variable; -
local -A
: Define an associative array;
For top-level global values outside of functions, declare
can be used in
place of local
. Top-level variables must stil be export
ed in order to be
visible to sub-shells.
stdlib.sh
is designed on the principal that successful function execution
elicits a return-code of zero, whilst non-zero indicates an error. Calling
exit
(or die
) is generally avoided in functions, leaving the caller to
decide on the severity of a failure. Code considered unreachable, if ever
executed, should return a canary value of 255
. Only values between 0
and
255
are valid return-codes - returning a negative value, sometimes seen in
code written by Java programmers particularly, will actually return 256
less
the absolute value of the return code.
It is suggested that all variables be enclosed within braces, and also
double-quoted unless defined as a numeric value with local -i
or
declare -i
. The exception to this is where a variable is used in place of a
command, where braces should be omitted in order to differentiate commands from
values.
local string="text"
output "${string:-}"
local -i rc=0
return ${rc}
declare DEBUG_RM="echo rm"
$DEBUG_RM "${files[@]}"
Function | Description |
---|---|
output() |
An alias for echo , used to indicate user-visible output |
respond() |
An alias for echo , used to indicate response feedback |
std::cleanup() |
Remove any temporary files created by the mktemp functions or added by std::garbagecollect |
std::usage-message() |
Provide custom help-text where ${std_USAGE} is not sufficient |
std::usage() |
Output help text from ${std_USAGE} or std::usage-message() |
std::wrap() |
Format text to wrap to the width of the console at a word-end - N.B. May require 'export COLUMNS' |
std::log() |
Output text to console, file, or syslog |
std::colour() |
Optionally determine intended text colour from string prefix, and return text with ANSI escapes |
die() |
Output text in a standardised format and exit with a failure code |
error() |
Output text in a standardised format |
warn() |
Output text in a standardised format |
note() |
Output text in a standardised format |
notice() |
Output text in a standardised format |
info() |
Output text in a standardised format |
debug() |
Output text in a standardised format |
symerror() |
errno: Provide symbol name (such as 'EERROR ') for specified code |
errsymbol() |
errno: Provide code for specified symbol |
strerror() |
errno: Provide description string for most recent or specified code |
std::garbagecollect() |
Add additional files for automatic removal on exit |
std::mktemp() |
OS-neutral standard mktemp(1) replacement, with garbage collection |
std::emktemp() |
Enhanced mktemp(1) replacemnt with improved syntax |
std::push() |
Push() implementation - see Martin Väth's original here |
std::readlink() |
Basic OS-neutral readlink(1) stand-in |
std::inherit |
Document the use of global variables |
std::define() |
Improved HEREDOC support, without the need to invoke cat |
std::formatlist() |
Return an English-formatted list with Oxford comma |
std::vcmp() |
Compare two specified versions, or output a list of versions and succeed if the list was sorted |
std::requires() |
Declare script command requirements/dependencies |
std::capture() |
Capture the output- or error- stream of a command |
std::ensure() |
Exit with a specified error message if a command fails |
std::silence() |
Execute a command and drop all output (e.g. >/dev/null 2>&1 ) |
std::wordsplit() |
Return each whitespace-separated element from the (quoted) input, without unexpected globbing |
std::findfile() |
Given an application name, file name, and default directory find a likely data file |
std::getfilesection() |
Retrieve a single section from a Windows-style .ini file with square-bracketed section titles |
std::parseargs() |
Allow functions to accept named parameters with only minor code-changes |
std::configure() |
Export variables containing the standard system paths as used by configure scripts |
std::http::squash() |
Map HTTP response codes (100-599) down to shell return codes (0-255) by squashing unused values |
std::http::expand() |
Map shell return codes (0-255) back up to HTTP response codes (100-599) |
The example file stdlib-colour.map
may be copied to /etc/stdlib/colour.map
or /etc/stdlib.colour.map
(or similar locations beneath /usr/local/etc
, or
even ~/.stdlib/colour.map
or ~/.stdlib.colour.map
- see std::findfile()
for details of how this flexibility can easily be achieved in your own scripts),
or an alternate location may be specified by setting the environment variable
STDLIB_COLOUR_MAP
to the full path to the map file. stdlib-colour.map
documents the available colour options.
The colour mappings from stdlib-colour.map
are used when stdlib.sh
is
sourced with the environment variable STDLIB_WANT_COLOUR
set - for backwards-
compatibility, colourised output is disabled by default.