This document describes version 1.3b5 of MBFL, a library of functions for the GNU Bash shell. The package is distributed under the terms of the GNU Lesser General Public License (LGPL); the home page is at:
and stable packages can be downloaded from:
while development takes place at:
Copyright © 2003-2005, 2009 by Marco Maggi marcomaggi@gna.org
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with Invariant Sections being “GNU Free Documentation License” and “GNU Lesser General Public License”, no Front–Cover Texts, and no Back–Cover Texts. A copy of the license is included in the section entitled “GNU Free Documentation License”.
Appendices
Indexes
--- The Detailed Node Listing ---
Overview of the package
Using the script preprocessor
Manipulating files and pathnames
File names
File Commands
Parsing command line options
Using external programs
Interfaces to external programs
Manipulating strings
Manipulating variables
Interfacing with the system
Main function
Building test suites
Handling files in tests
Examples for sending email
Using gnutls-cli as connector
This package is an attempt to make GNU Bash a viable solution for medium sized scripts. A set of modules implementing common operations and a script template are offered by this package and the author has used them with success in implementing non–small scripts.
The philosophy of MBFL is to do the work as much as possible without external commands. For example: string manipulation is done using the special variable substitution provided by Bash, and no use is done of utilities like sed, grep and ed.
The library is better used starting from the template script examples/template.sh. This is because with MBFL some choices have been made to reduce the application dependent part of the script to the smallest dimension; if we follow another schema, MBFL modules may be inadequate. This is especially true for the options parsing module.
The easiest way to use the library is to include at run time the library file libmbfl.sh in the script. To do it, we install the package on the system and use this code in the scripts:
source "${MBFL_LIBRARY:=$(mbfl-config)}"
after the service variables have been declared. Required user defined variables.
This code will read the full pathname of the library from the
environment variable MBFL_LIBRARY; if this variable is not set:
the script mbfl-config is invoked with no arguments to acquire
the pathname. mbfl-config is installed by the package in the
$(bindir) directory, which we must include in the PATH
environment variable.
A more reliable way to load the library is:
mbfl_INTERACTIVE=no
mbfl_LOADED=no
mbfl_INSTALLED=$(mbfl-config) &>/dev/null
mbfl_HARDCODED=./some/dir/libmbfl.sh
for item in \
"$MBFL_LIBRARY" "$mbfl_HARDCODED" "$mbfl_INSTALLED"
do
test -n "$item" -a -f "$item" -a -r "$item" && {
source "$item" &>/dev/null || {
printf '%s error: loading MBFL file "%s"\n' \
"$script_PROGNAME" "$item" >&2
exit 2
}
}
done
unset -v item
test "$mbfl_LOADED" = yes || {
printf '%s error: incorrect evaluation of MBFL\n' \
"$script_PROGNAME" >&2
exit 2
}
where the optional ./some/dir/libmbfl.sh can be the pathname of a known location in which to find the library. This code:
$ MBFL_LIBRARY=/path/to/libmbfl.sh script.sh
We see that the output of the source command is discarded: this is because we assume that:
This code is included in the MBFL distribution in the src/lib/loader.sh file.
Another solution is to directly include the library in the script; this is easy if we preprocess our scripts with GNU M4. We only need to put the following in the script:
m4_changequote([[,]])
m4_include(libmbfl.sh)
then preprocess the script with:
$ m4 --prefix-builtins --include=/path/to/library \
script.sh.m4 >script.sh
easy to do in a Makefile. The installation directory pathname of
the library (/path/to/library in the example) is the output of
mbfl-config --libpath.
It is also interesting to process the script with the following rule of GNU Make: assuming that the source scripts are in the src/modules directory of the source tree:
vpath %.sh.m4 $(srcdir)/src/modules
M4 = ...
M4FLAGS = --prefix-builtins --include=/path/to/library
%.sh: %.sh.m4
$(M4) $(M4FLAGS) $(<) | \
grep --invert-match -e '^#' -e '^$$' | \
sed -e "s/^ \\+//" >$(@)
this will remove all the comments and blank lines, decreasing the size
of the script significantly if one makes use of verbose comments; note
that this will wipe out the #!/bin/bash first line, too.
Usually we want the script to begin with #!/bin/bash followed by
a comment describing the license terms. We can do it by preparing a
script like the following:
#!/bin/bash
# ... license ...
m4_include(realscript.sh)
### end of file
and processing it with the following make rule:
M4 = ...
M4FLAGS = --prefix-builtins --include=/path/to/library
script.sh: script.sh.m4 realscript.sh
$(M4) $(M4FLAGS) $(<) >$(@)
realscript.sh can be processed as explained above.
At this point, though, it is better to use the MBFL preprocessor. Using the script preprocessor.
p_. It is guaranteed that MBFL never uses
variables with name starting with such a prefix.
To do this execute the following code in the shell before sourcing the library:
mbfl_INTERACTIVE=yes
without exporting the variable. The “dangerous” blocks of code in the library are surrounded by:
test "$mbfl_INTERACTIVE" = yes || {
...
}
so they are not executed. Disabled modules are: main, getopts, signal; “disabled” does not mean that the functions are not there: only the global variable declarations are excluded; this is a little dirty, but, at present, it should work.
read built in does word splitting on the text it reads, even
when there is a single output variable. We can verify it with:
printf '\tciao\n' | {
read line
echo line was "'$line'"
}
-| line was 'ciao'
we see that the initial tabulation character has been stripped, because
it is a character in the deafult value of IFS. Word splitting
does not happen when we set IFS to the empty string:
printf '\tciao\n' | {
IFS= read line
echo line was "'$line'"
}
-| line was ' ciao'
To avoid the mutation of text read with read, MBFL always sets
IFS to the empty string in the environment in which read
is evalulated. This happens in functions like
mbfl_read_maybe_null and mbfl_dialog_ask_password;
notice, though, that these functions do not mutate the
IFS value in the environment of the caller.
The MBFL script preprocessor is a command line program named mbflpp.sh; it is itself a Bash script that makes use of the MBFL library. It can remove comments, blank lines, blank characters at the beginning of lines, and it can expand macros.
The macro preprocessing is done with the GNU m4 program, using a library of macros called preprocessor.m4 which is installed on the system with the MBFL package.
The use of the preprocessor is fully optional: every feature of the MBFL library can be used without the preprocessor.
The basic synopsis is:
mbflpp.sh [options] <INFILE >OUTFILE
mbflpp.sh [options] --outfile=OUTFILE [--] INFILE1 INFILE2 ...
all the INFILE pathnames are interpreted as files to concatenate
in the specified order.
All the MBFL built in command line options are available, additionally the following options are supported.
--preserve-comments--add-bash#!/usr/local/bin/bash at the beginning of the output.
--define=VALUE--include=VALUEVALUE must be a directory pathname
and it is handed to M4 to search for macro files. Can be used multiple
times.
--library=VALUEVALUE must be the name of an M4 macro
file which is evaluated before the input files.
-oVALUE--output=VALUE- for stdout.
-e--evalThe M4 preprocessor is invoked by mbflpp.sh with the
--prefix-builtins option; so all the M4 built in macros and
directives are available prefixed with m4_.
Define a variable local to a shell function holding an argument to the function. varname is the name of the variable; number is the positional parameter number; name is a description of the argument.
Example, the following:
mandatory_parameter(PATHNAME, 2, file pathname)is expanded to:
local PATHNAME=${2:?"missing file pathname parameter to '${FUNCNAME}'"}Another example the following function:
function message () { mandatory_parameter(PROGNAME, 1, program name) mandatory_parameter(STRING, 2, message string) printf '%s: %s\n' "${PROGNAME}" "${STRING}" }is expanded to:
function message () { local PROGNAME=${1:?"missing program name parameter to '${FUNCNAME}'"} local STRING=${2:?"missing message string parameter to '${FUNCNAME}'"} printf '%s: %s\n' "${PROGNAME}" "${STRING}" }and so it is a function with two mandatory parameters.
Define a variable local to a shell function holding an argument to the function. varname is the name of the variable; number is the positional parameter number; default_value is the initialisation value for the variable if the argument is not used.
Example, the following:
optional_parameter(COUNT, 2, 123)is expanded to:
local count=${2:-123}"
Define a variable local to a shell function holding an argument to the script. varname is the variable name, argindex is the argument index in the
ARGVarray.Example, the following:
command_line_argument(PATHNAME,3)is expanded to:
local PATHNAME="${ARGV[3]}"
The following variables are expected to be defined before the MBFL code is evaluated. They are used by MBFL to compute values for its own variables.
The identifier of the license under which the script is released. Accepted values are:
GPLorGPL2,GPL3,LGPLorLGPL2,LGPL3,BSD. It is used to select the appropriate value to be displayed when the user of the script selects the--licenseoption. Predefined options
One line string providing a brief description of the program. It is used in the help screen (the one echoed when the --help option is used) just after the content of
script_USAGE.
One or more lines of text to be displayed at the end of the help screen, after the options description. It should contain examples of common invocations for the script.
The string is used as first argument to
printf, so escape sequences like\tand\nare expanded.
All the text in these variables is used as argument to the printf
built–in command; in particular: script_DESCRIPTION and
script_EXAMPLES are used as first argument to printf, so
the escape sequences are evaluated.
The following example shows how to declare the variables.
script_PROGNAME=myscript.sh
script_AUTHOR='Marco Maggi and Marco Maggi'
script_COPYRIGHT_YEARS='2002, 2003, 2004'
script_VERSION=1.0
script_LICENSE=GPL3
script_USAGE="usage: $script_PROGNAME [options] ..."
script_DESCRIPTION='Does this and that.'
script_EXAMPLES='Examples:
\tmyscript.sh --do-something arg arg ...
\tmyscript.sh --do-other arg arg ...'
Notice that script_DESCRIPTION and script_EXAMPLES
do not end with a newline character.
Set a variable named name to value, but only if name is not the empty string.
Read a line from its stdin and store it in a variable named varname. If
mbfl_option_nullreturns true: the null character is used as terminator, like in:IFS= read -d $'\x00'
The purpose of this module is to let an external process invoke a Bash script with damned command line arguments: strings including blanks or strange characters that may trigger quoting rules.
This problem can arise when using scripting languages with some sort of
eval command.
The solution is to encode the argument string in hexadecimal or octal format strings, so that all the damned characters are converted to “good” ones. The the Bash script can convert them back.
Example:
mbfl_decode_hex 414243
-> ABC
Extract the extension from a file name. Search the last dot character in the argument string and echo to stdout the range of characters from the dot to the end, not including the dot. If a slash character or the beginning of the string is found first: echoes to stdout the empty string.
Extract the directory part from a fully qualified file name. Search the last slash character in the input string and echo to stdout the range of characters from the first to the slash, not including the slash.
If no slash is found: echo a single dot (the current directory).
If the input string begins with
/or//with no slash characters after the first ones: echo a single slash.
Extract the root portion of a file name. Search the last dot character in the argument string and echo to stdout the range of characters from the beginning to the dot, not including the dot.
If a slash character is found first, or no dot is found, or the dot is the first character: echo the empty string.
Extract the file portion from a fully qualified file name. Search the last slash character in the input string and echo to stdout the range of characters from the slash to the end, not including the slash. If no slash is found: echo the whole string.
Separate a file name into its components. One or more contiguous occurrences of the slash character is used as separator. The components are stored in an array named
SPLITPATH, that may be declaredlocalin the scope of the caller; the base index is zero. The number of elements in the array is stored in a variable namedSPLITCOUNT. Return true.
If the last character in pathname is a slash (
/): remove it and print the result on stdout.
Normalise a file name: remove all the occurrences of
.and...If pathname is relative (according to
mbfl_file_is_absolute) and prefix is not present or it is the empty string: the current process working directory is prepended to pathname.If prefix is present and non empty, and pathname is relative (according to
mbfl_file_is_absolute): prefix is prepended to pathname and normalised, too.Echo to stdout the normalised file name. Return true.
If pathname is a subdirectory or file under basedir: print to stdout the subpathname portion. Example:
mbfl_file_subpathname /a/b/c /a -> ./b/cBoth pathname and basedir must be full (normalised) pathnames for this function to work.
If pathname is recognised as subpathname of basedir: the return code is zero; else the return code is one.
Return true if the first character in pathname is a slash (
/); else returns false.
Return true if pathname is a directory according to
mbfl_file_is_directoryand an absolute pathname according tombfl_file_is_absolute.
Return true if pathname is a file according to
mbfl_file_is_fileand an absolute pathname according tombfl_file_is_absolute.
Find a value for a temporary directory according to the following rules:
- If PATHNAME argument is not used: it defaults to the current value of
mbfl_option_TMPDIR.- If PATHNAME is not null and it is a directory and it is writable: it is accepted as value.
- If PATHNAME argument is invalid: the value /tmp/$USER, where USER is the environment variable, is tried; finally the value /tmp is tried.
Echo the accepted value to stdout. Return true if a value is found, false otherwise.
Declare the commands required to retrieve informations about files and directories. Declaring the intention to use a program
The programs are: ls, readlink.
Make use of the readlink to normalise the pathname of a symbolic link (remember that a symbolic link references a file, never a directory). Echo to stdout the normalised pathname.
The command line of readlink is:
readlink -fn $pathname
Make use of readlink to acquire the original pathname referenced by pathname, then print it.
Declare the commands required to create directories. Declaring the intention to use a program
The programs are: mkdir.
Create a directory named pathname; all the non–existent parents are created, too. If permissions is present: it is the specification of directory permissions in octal mode.
This function does not test if the directory already exists: the command is always executed.
Wrapper for
mbfl_file_make_directorythat creates the directory if it does not exist.If a sudo user was requested: this function resets the request even if no command has been executed.
At present, copying of directories is not supported; we have to create the directory and then copy files into it.
Declare the commands required to copy files and directories. Declaring the intention to use a program
The programs are: cp.
Copy the source, a file pathname, to target, a file pathname. Additional arguments are handed to the command unchanged.
If source does not exist, or if it is not a file, an error is generated and the return value is 1. If target exists an error is generated and the return value is 1.
Copy the source, a file pathname, into the directory target. Additional arguments are handed to the command unchanged.
If source does not exist, or if it is not a file, an error is generated and the return value is 1. If target does not exist or it is not a directory: an error message is generated and the return value is 1.
Declare the commands required to move files and directories. Declaring the intention to use a program
The programs are: mv.
Move the source, a file or directory, to target, a pathname. Additional arguments are handed to the command unchanged.
If source does not exist, or if it is not readable, an error is generated and the return value is 1. If target exists an error is generated and the return value is 1.
Move the source, a file or directory, into the directory target. Additional arguments are handed to the command unchanged.
If source does not exist, or if it is not readable, an error message is generated and the return value is 1. If target does not exist or it is not a directory: an error is generated and the return value is 1.
Files removal is forced: the --force option to rm is
always used. It is responsibility of the caller to validate the
operation before invoking these functions.
Some functions test the existence of the pathname before attempting to remove it: this is done only if test execution is disabled; if test execution is enabled the command line is echoed to stderr to make it easier to debug scripts.
Declare the commands required to remove files and directories. Declaring the intention to use a program
The programs are: rm, rmdir.
Remove pathname, no matter if it is a file or directory. If it is a directory: descend the sub-levels removing all of them. If an error occurs return 1.
Remove the file or symbolic link selected by pathname. If the file does not exist or it is not a file or an error occurs: return 1.
Remove the symbolic link selected by pathname. If the link does not exist or it is not a symbolic link or an error occurs: return 1.
Remove the file or symbolic link selected by pathname. If the file does not exist or it is not a file or an error occurs: return 1.
Remove the directory selected by pathname. If the directory does not exist or an error occurs: return 1.
Like
mbfl_file_remove_directory, but do not print messages if the directory is not empty.
Declare the commands required to create symbolic links. Declaring the intention to use a program
The programs are: ln.
Remember that when we execute a script with the --test option: the external commands are not executed: a command line is echoed to stdout. It is recommended to use this mode to fine tune the command line options required by tar.
Execute tar with whatever arguments are used. Return the return code of tar.
Create an archive and send it to stdout. The root of the archive is the directory. Files are selected with the
.pattern. tar flags may be appended to the invocation to this function. In case of error return 1.
Read an archive from stdin and extract it under directory. tar flags may be appended to the invocation to this function. In case of error return 1.
Read an archive from a file and extract it under directory. tar flags may be appended to the invocation to this function. In case of error return 1.
Create an archive named archive holding the contents of directory. Before creating the archive, the process changes the current directory to directory and selects the files with the pattern
.. tar flags may be appended to the invocation to this function. In case of error return 1.
Like
mbfl_tar_create_to_filebut archive all the contents of directory, including the directory itself (not its parents).
Print to stdout the list of files in archive. tar flags may be appended to the invocation to this function. In case of error return 1.
Declare the intention to use the programs required to get/set file permissions. Declaring the intention to use a program
The programs are: ls, chmod, cut.
Print the access permissions for pathname, in octal format.
To set permissions one may request the use of sudo: just request a user before invoking the following function. Executing a program
Set the access permissions for pathname; mode must be in a form accepted by chmod.
This module has an internal state, stored in global variables. The state holds informations about:
Declare the programs required to compress a file. Declaring the intention to use a program
The programs are: gzip, bzip2.
The selection will affect all the future invocations of the compression/decompression functions.
Select a compressor program.
Select whether the compress program should keep the original file or not.
Select if the output must be sent to stdout or a file. This takes precedence over the keep/no keep configuration: if the output is sent to stdout the original file is kept.
Additional arguments to the selected compressor may be appended to the invocation of the following functions and are handed to the compressor unchanged.
Compress PATHNAME, a file pathname, with the currently selected compressor program.
Decompress PATHNAME, a file pathname, with the currently selected compressor program.
When using the following functions: The optional argument
PRINT_ERROR will cause an error message to be printed with
mbfl_message_error if the test fails; the argument value must be
print_error.
Return true if filename is not the empty string and is a file.
Return true if filename is not the empty string, is a file and is readable.
Return true if filename is not the empty string, is a file and is writable.
Return true if filename is not the empty string, is a file and is executable.
Return true if directory is not the empty string and is a directory.
Return true if directory is not the empty string, is a directory and is readable.
Return true if directory is not the empty string, is a directory and is writable.
Return true if directory is not the empty string, is a directory and is executable.
Test directory existence and writability; return true if the directory exists and is writable. If the condition is not met: Print informative messages using description to refer to the directory.
Return true if pathname is not the empty string and is a symbolic link.
Return true if pathname is not the empty string and is readable.
Return true if pathname is not the empty string and is writable.
Return true if pathname is not the empty string and is executable.
The following functions perform actions that can normally be done directly with the redirection operators of Bash:
# write to a file
printf '%s' "$string" >"$filename"
# append to a file
printf '%s' "$string" >>"$filename"
# read a file, print contents
printf '%s' "$(<$filename)"
The functions act differently in that they spawn a bash
subprocess, by invoking mbfl_program_exec, and let it do the
operation; this allows us to request the usage of sudo and so
to read and write files with modified privileges, but only for the time
needed to do the operation, not for the whole script.
Write string to filename, eventually creating it or overwriting old contents.
Change directory to dirname. Optional flags to cd may be appended.
Wrapper for
mbfl_change_directory. If verbose mode is on: print a message.
The getopt module defines a set of procedures to be used to process command line arguments with the following format:
-aa with no value.
-a123a with value 123.
--biancobianco with no value.
--color=biancocolor with value bianco.
The module contains, at the root level, a block of code like the following:
ARGC=0
declare -a ARGV ARGV1
for ((ARGC1=0; $# > 0; ++ARGC1))
do
ARGV1[$ARGC1]=$1
shift
done
this block is executed when MBFL (and the script that loads it) is
evaluated. Its purpose is to store command line arguments in the global
array ARGV1 and the number of command line arguments in the
global variable ARGC1.
The global array ARGV and the global variable ARGC are
predefined and should be used by the mbfl_getopts_* functions to
store non–option command line arguments.
Example:
$ script --gulp wo --gasp=123 wa
if the script makes use of MBFL, the strings wo and wa
will go into ARGV and ARGC will be set to 2. The option
arguments are processed and some action is performed to register them.
We can access the non–option arguments with the following code:
for ((i=0; $i < $ARGC; ++i))
do
# do something with ${ARGV[$i]}
done
When using action arguments: the first non–option argument can be
interpreted as special value that selects an action to be performed by
the script. In this case the first argument is removed from the
ARGV array, so that processing the other arguments is not
affected.
To use this module we have to declare a set of script options and
optionally a set of action arguments. We declare a new script option
with the function mbfl_declare_option and a new action argument
with the function mbfl_declare_action_argument.
Option and action argument declarations should be done at the beginning of the script, before doing anything else; for example: right after the MBFL library code.
In the main block of the script, options are parsed by invoking
mbfl_getopts_parse: this function will update global variables
and invoke a script function for each option on the command line. It
can also select a function to be invoked as the main action of the
script.
Example of option declaration:
mbfl_declare_option ALPHA no a alpha noarg "enable alpha option"
this code declares an option with no argument having properties:
script_option_ALPHA, which will be set to
no by default and to yes if the option is used.
enable alpha option, to be shown in the usage help
screen.
If the option is used: the function script_option_update_alpha is
invoked (if it exists) with no arguments, after the variable
script_option_ALPHA has been set to yes. Valid option
uses are:
$ script.sh -a
$ script.sh --alpha
Example of option declaration:
mbfl_declare_option BETA 123 b beta witharg "select beta value"
this code declares an option with argument having properties:
script_option_BETA, which will be set to
123 by default and to the value selected on the command line if
the option is used.
select beta value, to be shown in the usage output.
If the option is used: the function script_option_update_beta is
invoked (if it exists) with no arguments, after the variable
script_option_BETA has been set to the selected value. Valid
option uses are:
$ script.sh -b456
$ script.sh --beta=456
A special option example:
mbfl_declare_option ACTION_GAMMA \
no g gamma noarg "do gamma action"
mbfl_declare_option ACTION_DELTA \
yes d delta noarg "do delta action"
this code declares two options with no arguments; the difference from
the other declarations is that the keywords are prefixed with
ACTION_: this prefix is recognised by the module and causes, if
the option is used on the command line, the following code to be
evaluated at argument parsing time:
mbfl_main_set_main script_action_gamma
or:
mbfl_main_set_main script_action_delta
where the argument script_action_gamma is built by prefixing the
lower case version of the keyword with script_. The code selects
a function as main function for the script. Driving script execution.
Additionally, if the default value is yes: the main function is
selected at declaration time (that is by mbfl_declare_option);
this is useful to declare an action option and select automatically the
action function. In the example: the function
script_action_delta is selected as main action function.
It is an error to declare a keyword prefixed with ACTION_ with an
option with argument.
The following example declares two action arguments:
mbfl_declare_action_argument gamma gamma no no "do gamma action"
mbfl_declare_action_argument delta delta yes no "do delta action"
an action argument is a non–option argument that is used on the command line as first parameter; its purpose is to select a function to be evaluated as main action of the script.
The first of the two declarations has the following properties:
gamma (the first one); this is the
internal identifier of the argument used to build the associated action
function name.
gamma (the second one).
no (the first one): this script action is
not selected by default.
no).
do gamma action, to be shown in the usage output.
If the first parameter to the script is gamma or delta,
the following code is evaluated at argument parsing time:
mbfl_main_set_main script_action_gamma
or:
mbfl_main_set_main script_action_delta
where the argument script_action_gamma is built by prefixing the
the keyword with script_action_. The code selects a function as
main function for the script. Driving script execution.
Additionally, if the selection value is yes: the main function is
selected at declaration time (that is by
mbfl_declare_action_argument); this is useful to declare an
action option and select automatically the action function. In the
example: the function script_action_delta is selected as main
action function.
Notice that action arguments and action options implement the same feature and are fully compatible with each other. Both can be used to select an action for the script.
When declaring action options and action arguments selected by default: the last one wins. So the following:
mbfl_declare_option ACTION_GAMMA \
yes g gamma noarg "do gamma action"
mbfl_declare_action_argument delta \
delta yes no "do delta action"
defines a --gamma action option and a delta action
argument, and the main function is set to script_action_delta.
However, it is bad interface design to use both of them; it is better to use action arguments or action options.
A set of predefined options is recognised by the library and not handed to the user defined functions.
mbfl_option_TMPDIR.
If this option is used: the values are decoded by
mbfl_getopts_parse before storing them in the ARGV
array and before being stored in the option's specific global variables.
mbfl_option_verbose returns true. Printing messages to the console.
mbfl_option_verbose returns false.
mbfl_option_verbose_program returns true or false depending on
the state of this option.
mbfl_option_NULL is set to yes.
mbfl_option_INTERACTIVE is set to no.
mbfl_option_INTERACTIVE is set to yes.
NAME.
CODE.
mbfl_message_VERSION, then exits with code zero. The
variable makes use of the service variables. Required user defined variables.
script_VERSION, then exits with code zero. Required user defined variables.
mbfl_message_LICENSE_*, then exits with code
zero. The variable makes use of the service variables. Required user defined variables.
script_USAGE; a newline; the string options:; a
newline; an automatically generated string describing the options
declared with mbfl_declare_option; a string describing the
MBFL default options; the contents of the global variable
script_EXAMPLES. Then exits with code zero. Required user defined variables.
script_USAGE; a newline; the string options:; a
newline; an automatically generated string describing the options
declared with mbfl_declare_option. Then exits with code zero.
The difference with --help is that predefined options and usage
examples are not displayed.
mbfl_getopts_print_long_switches, then exit the script with code
zero.
mbfl_getopts_print_action_arguments, then exit the script with
code zero.
The following functions may be used to set, unset and query the state of the predefined options.
Query/set/unset the encoded arguments option.
mbfl_option_encoded_argsreturns true if the option --encoded-args was used on the command line.
Query/set/unset the verbose messages option.
mbfl_option_verbosereturns true if the option --verbose was used on the command line after all the occurrences of --silent; it returns false if the option --silent was used on the command line after all the occurrences of --verbose.
Query/set/unset verbose execution for external programs.
This option, of course, is supported only for programs that are known by MBFL (like rm): if a program is executed with
mbfl_program_exec, it is responsibility of the caller to use the option.
Print the command line of executed external program. This does not disable program execution, it just prints the command line before executing it.
Query/set/unset the test execution option.
Query/set/unset the debug messages option.
Query/set/unset the null list separator option.
Query/set/unset the interactive execution option.
mbfl_option_interactivereturns true if the option --interactive was used on the command line after all the occurrences of --force; it returns false if the option --force was used on the command line after all the occurrences of --interactive.
The following are special option functions.
Save the current state of the test option then invokes
mbfl_unset_option_test.
Restore the state of the test option to the one before the invocation to
mbfl_option_test_save.
Every declared option should have a long switch, the brief switch may be omitted.
Declare a new option. Arguments description follows.
- keyword
- A string identifying the option; internally it is used to build a function name and a variable name. It is safer to limit this string to the letters in the ranges
a-z,A-Zand underscores.- default
- The default value for the option. For an option with argument it can be anything; for an option with no argument: it must be
yesorno.- brief
- The brief option selector: a single character. It is safer to choose a single letter (lower or upper case) in the ASCII standard.
- long
- The long option selector: a string. It is safer to choose a sequence of letters in the ASCII standard, separated by underscores or dashes.
- hasarg
- Either
withargornoarg: declares if the option requires an argument or not.- description
- A one–line string briefly describing the option.
Declare an action argument. Arguments description follows.
- keyword
- A string identifying the argument; internally it is used to build a function name. It must be a valid Bash function identifier.
- string
- The string that identifies the argument on the command line of the string. It has limitations in format:
- Its first character must be a letter in the range
a-zorA-Z.- All the characters but the first must be: letters in the range
a-zorA-Z; numbers in the range0-9; dash characters-.- selected
- A boolean value that can be
yesorno. Ifyesthe function associated to this action argument is selected by default as main action of the script. The function name is built by concatenatingscript_action_with keyword.- skipopts
- A boolean value that can be
yesorno. Ifno: command line options are parsed as usual; ifyeswhen this action argument is found as first parameter to the script, the rest of the arguments are not parsed, but copied as is to theARGVandARGCvariables. This allows to hand options not recognised by the script to some external program.- description
- A one–line string briefly describing the argument.
Parse a set of command line options. The options are handed to user defined functions. The global array
ARGV1and the global variableARGC1are supposed to hold the command line arguments and the number of command line arguments. Non–option arguments are left in the global arrayARGV, the global variableARGCholds the number of elements inARGV.If an action argument is recognised as the first element in
ARGV1: it is processed but not added toARGVand not counted inARGC.
Verify if a string has the format of a long option without argument. string is the string to validate. The optional varname is the name of a variable that this function will set to the option name from string, without the leading dashes.
Return with code zero if the string is a long option without argument, else returns with code one.
An option must be of the form
--option, only characters in the rangesA-Z,a-z,0-9and the characters-and_are allowed in the option name.Usage examples:
mbfl_getopts_islong --option ⇒ 0 mbfl_getopts_islong --option=123 ⇒ 1 mbfl_getopts_islong gasp ⇒ 1
Verify if a string has the format of a long option with argument. Arguments:
- string
- The string to validate.
- optname
- Optional name of a variable that this function will set to the option name from string, without the leading dashes.
- varname
- Optional name of a variable that this function will set to the option value from string.
Return with code zero if the string is a long option with argument, else return with code one.
An option must be of the form
--option=value, only characters in the rangesA-Z,a-z,0-9and the characters-and_are allowed in the option name.If the argument is not an option with value, the variable names are ignored.
Usage examples:
mbfl_getopts_islong_with --option=one ⇒ 0 mbfl_getopts_islong_with --option ⇒ 1 mbfl_getopts_islong_with wappa ⇒ 1
Verify if a string has the format of a brief option without argument. string is the string to validate. The optional varname is the name of a variable that this function will set to the option name from string, without the leading dash.
Return with code zero if the argument is a brief option without argument, else return with code one.
A brief option must be of the form
-a, only characters in the rangesA-Z,a-z,0-9are allowed as option letters.Usage examples:
mbfl_getopts_isbrief -o ⇒ 0 mbfl_getopts_isbrief -o123 ⇒ 1 mbfl_getopts_isbrief gasp ⇒ 1
Verify if a string has the format of a brief option with argument. Arguments:
- string
- The string to validate.
- optname
- Optional name of a variable that this function will set to the option name from string, without the leading dashes.
- valname
- Optional name of a variable that this function will set to the option value.
Return with code zero if the argument is a brief option without argument, else return with code one.
A brief option must be of the form
-aV(ais the option,Vis the value), only characters in the rangesA-Z,a-z,0-9are allowed as option letters.Usage examples:
mbfl_getopts_isbrief_with -o123 ⇒ 0 mbfl_getopts_isbrief_with -o ⇒ 1 mbfl_getopts_isbrief_with --option ⇒ 1 mbfl_getopts_isbrief_with wappa ⇒ 1
Verify if a string has the format of an action argument. string is the string to validate. Return with code zero if the argument is an action argument, else return with code one.
Usage examples:
mbfl_getopts_action_argument alpha ⇒ 0 mbfl_getopts_action_argument do-this ⇒ 0 mbfl_getopts_action_argument -o ⇒ 1 mbfl_getopts_action_argument -o123 ⇒ 1 mbfl_getopts_action_argument --option ⇒ 1 mbfl_getopts_action_argument --option=one ⇒ 1
Validate the number of arguments. required is the required number of arguments, present is the given number of arguments on the command line. If the number of arguments is different from the required one: print an error message and return with code one; else return with code zero.
Validate the number of arguments. argc must be between min_required and max_required, inclusive.
If the
ARGCglobal variable is set to zero: fills the global variableARGVwith lines read from stdin. If the global variablembfl_option_NULLis set toyes: lines are read using the null character as terminator, else they are read using the standard newline as terminator.This function may block waiting for input.
Check that all the arguments in
ARGVare file names of existent files. Return with code zero if no errors, else print an error message and return with code 1.
Print all the long switches in a row, separated by spaces. This is useful to retrieve the option for Bash programmable completion.
Print all the action arguments strings in a row, separated by spaces. This is useful to retrieve the option for Bash programmable completion.
This module allows us to print messages on an output channel. Various
forms of message are supported. All the function names are prefixed
with mbfl_message_. All the messages will have the forms:
<progname>: <message>
<progname>: [error|warning]: <message>
Set the script official name to put at the beginning of messages. This value is initialised to
script_PROGNAME.
Select the channel to be used to output messages. This value is initialised to 2, which is stderr.
Output a message to the selected output channel. Echo a string composed of: the selected program name, a colon, a space, string. No newline character is appended to the message. Escape characters supported by
printfare allowed in string.
Output a message to the selected output channel, but only if the evaluation of the function
mbfl_option_verbosereturns true.Echo a string composed of: the selected program name, a colon, a space, string. No newline character is appended to the message. Escape characters supported by
printfare allowed in string.
Output a message to the selected output channel, but only if the evaluation of the function
mbfl_option_verbosereturns true. Echo the string. No newline character is appended to the message. Escape characters supported byprintfare allowed in string.
Output a message to the selected output channel, but only if the evaluation of the function
mbfl_option_debugreturns true. Echo a string composed of: the selected program name, a colon, a space, string. No newline character is appended to the message. Escape characters supported byprintfare allowed in string.
Format the arguments in the same way
printfwould do; output the resulting string to the selected output channel, but only if the evaluation of the functionmbfl_option_debugreturns true.Echo a string composed of: the selected program name, a colon, a space, the string
debug, a colon, a space, the formatting result. No newline character is appended to the message.
Format the arguments in the same way
printfwould do; output the resulting string to the selected output channel, but only if the evaluation of the functionmbfl_option_verbosereturns true.Echo a string composed of: the selected program name, a colon, a space, the formatting result. No newline character is appended to the message.
Output a warning message to the selected output channel. Echo a string composed of: the selected program name, a colon, a space, the string
warning, a colon, a space, string, a newline character. Escape characters supported byprintfare allowed in string.
Format the arguments in the same way
printfwould do; output the resulting string to the selected output channel.Echo a string composed of: the selected program name, a colon, a space, the string
warning, a colon, a space, the formatting result. No newline character is appended to the message.
Output an error message to the selected output channel. Echo a string composed of: the selected program name, a colon, a space, the string
error, a colon, a space, string, a newline character. Escape characters supported byprintfare allowed in string.
Format the arguments in the same way
printfwould do; output the resulting string to the selected output channel.Echo a string composed of: the selected program name, a colon, a space, the string
error, a colon, a space, the formatting result. No newline character is appended to the message.
MBFL allows a script to execute a “dry run”, that is: do not perform any operation on the system, just print messages describing what will happen if the script is executed with the selected options. This implies, in the MBFL model, that no external program is executed.
When this feature is turned on: mbfl_program_exec does not
execute the program, instead it prints the command line on standard
error and it returns true.
Enable the script test option. After this: a script must not mutate the system in any way, it should just print messages describing the operations.
However, the script is allowed to acquire informations from the system; for example it can acquire the list of files in a directory or load the contents of a file.
This function is invoked when the predefined option --test is used on the command line.
Disable the script test option. After this a script must perform normal operations.
The simpler way to test the availability of a program is to look for it just before it is used.
A wrapper for:
type -ap programthat looks for a program in the current search path. It prints the full pathname of the program found, or an empty string if nothing is found.
program may be a program name with no directory part (examples: sed, grep), or an absolute or relative pathname (examples: /bin/sed, ../bin/grep). If program is a relative pathname and it is an executable file: the output of the function is exactly program, it is not normalised.
If an alias exists for a program,
type -pprogram will return the empty string; that is why we have to usetype -approgram, which will return the correct file pathname.If the environment variable PATH holds the same directory more than once, or there exist programs with the same name in more than one PATH elements:
type -ap $programwill return more than one line of output, one for each executable file found. The first value (the first line) is the one selected by this function.
The use of this function is deprecated.
Check the availability of programs. All the pathnames on the command line are checked: if one is not executable an error message is printed on stderr. Return false if a program cannot be found, true otherwise.
This module provides an API to execute a program under the privileges of the current user or under a more or less privileged user; it makes use of sudo, to allow one to execute a program as a different user (optionally without entering a password): refer to the sudo documentation for the required configuration.
The functions described here must be used in the following way:
mbfl_program_exec ls /bin
# At the beginning of the script:
mbfl_program_enable_sudo
# When executing a program:
mbfl_program_declare_sudo_user root
mbfl_program_exec ls /root
Every time we execute a program with sudo: we have to select
the user under which to execute it; if we do not do it: the internally
registered user defaults to nosudo, which tells the function not
to use sudo. So the following script works as commented:
mbfl_program_enable_sudo
# This is executed with the privileges of the user that
# launched the script.
mbfl_program_exec ls /bin
mbfl_program_declare_sudo_user root
# This is executed with root privileges.
mbfl_program_exec ls /root
# This is executed with the privileges of the user that
# launched the script.
mbfl_program_exec ls /bin
Evaluate a command line. program identifies an executable file: it can be the program name, or a relative or absolute pathname. The optional arg values are command line arguments that are handed to the program unchanged.
If usage of sudo was requested, the command is executed with it; then the sudo request is reset. This means that this function “consumes” a sudo request.
See below for the redirection of the standard error channel.
If the function
mbfl_option_testreturns true: instead of evaluation, the command line is sent to stderr.If the function
mbfl_option_show_programreturns true: the command line is sent to stderr, then it is executed.
Does all the same things of
mbfl_program_exec, running the given command line as:program arg ... <inchan >ouchan &additionally: set the global variable
mbfl_program_BGPIDto the process id of the background process; that is:mbfl_program_BGPIDis the value of$!right after the process execution.Using this function is different from calling:
mbfl_program_exec ls <inchan >ouchan &because doing so puts in the background the function call (in a subshell) and then runs the program.
Used by
mbfl_program_execbgto store the process id of the program executed in background.
Declare the intention to use sudo and other commands required to use it. Declaring the intention to use a program.
The declared programs are: sudo, whoami.
Register user as the user under which to execute the next program through sudo; the user will be selected using the
-uoption of sudo. The valuenosudomeans: do not use sudo.When the time comes: if the selected user name equals the value printed by whoami, sudo is not used.
Reset the previously requested sudo user to a value that will cause sudo not to be used in the next program invocation. This is useful to abort a user request.
Return true if the usage of sudo has been requested for the next command execution.
Execute bash with the arg arguments appended. The bash pathname is registered in the library at start up, from the built in variable
BASH.
Execute command in a bash subprocess, using the
-cswitch. The bash pathname is registered in the library at start up, from the built in variableBASH.
There are programs that output useful informations on their stderr channel (example: the at command).
Just for the next invocation to
mbfl_program_execredirect stderr to stdout, that is: use the2>&1redirection for the executed program.
This is useful because redirecting the output of
mbfl_program_exec:
echo ls | \
mbfl_program_exec at 'now +25 minutes' 2>&1 | \
while read
redirects also the “show program” output (getopts options for
the --show-program option explanation and see the above
description of mbfl_program_exec). Instead By using:
mbfl_program_redirect_stderr_to_stdout
echo ls | \
mbfl_program_exec at 'now +25 minutes' | \
while read
the “show program” output goes to stderr and the stderr output of the
at command is, internally, redirected to the stdout of
mbfl_program_exec.
To make a script model simpler, we assume that the unavailability of a program at the time of its execution is a fatal error. So if we need to execute a program and the executable is not there, the script must be aborted on the spot.
Functions are available to test the availability of a program, so we can try to locate an alternative or terminate the process under the script control. On a system where executables may vanish from one moment to another, no matter how we test a program's existence, there's always the possibility that the program is not “there” when we invoke it.
If we just use mbfl_program_exec to invoke an external
program, the function will try and fail if the executable is
unavailable: the return code will be false.
The vanishing of a program is a rare event: if it's there when we look for it, probably it will be there also a few moments later when we invoke it. For this reason, MBFL proposes a set of functions with which we can declare the intention of a script to use a set of programs.
A command line option is predefined to let the user test the availability of all the declared programs before invoking the script. Predefined options.
Register program as the name of a program required by the script;
mbfl_program_findis used to locate the program on the system. Checking programs existence.If program is a file name with no directory part (examples: sed, grep) the selected program is the full pathname of the file in one of the directories of PATH.
If program is a relative pathname (examples: ../bin/sed, ./grep): the selected program is the full pathname of the file normalised by this function with respect to the current working directory (with a call to
mbfl_file_normalise).The return value is always zero.
Validate the existence of all the declared programs. The return value is zero if all the programs are found, one otherwise.
This function is invoked by
mbfl_getopts_parsewhen the --validate-programs option is used on the command line.It may be a good idea to invoke this function at the beginning of a script, just before starting to do stuff, example:
mbfl_program_validate_declared || \ exit_because_program_not_foundIf verbose messages are enabled: a brief summary is echoed to stderr; from the command line the option --verbose must be used before --validate-programs.
Print the pathname of the previously declared program. Return zero if the program was found, otherwise print an error message and exit the current (sub)shell by invoking
exit_because_program_not_found.This function should be used to retrieve the pathname of the program to be used as first argument to
mbfl_program_exec:function program_wrapper () { local ARGUMENT PROGNAME FLAGS ARGUMENT=${1:?"missing 'ARGUMENT'"} shift PROGNAME=$(mbfl_program_found myprog) || \ exit $? FLAGS mbfl_option_verbose_program && \ FLAGS="$FLAGS --verbose" mbfl_program_exec "$PROGNAME" \ $FLAGS "$ARGUMENT" "$@" }Remember that we cannot use:
local PROGNAME=$(mbfl_program_found 'myprog') || \ exit $?because
localwill return with code zero even ifmbfl_program_foundfails, so the error will not be reported.
This section documents the interface to the atd daemon; we may want to read the at(1) manual page. The at service allows a user to schedule commands to be executed at a later time.
This interface is suitable for scripts that define a unique simple policy to schedule commands; example: at each run they schedule a command in a fixed queue, to be executed at a fixed time in the future.
This is good to implement the logic: if a condition does not happen
before time T, then execute command C.
Declare the intention to use the at interface. Declaring the intention to use a program.
The declared programs are: at, atq, atrm.
Return true if letter is a valid queue identifier, else return false.
Return true if the currently selected queue identifier is valid, else print an error message and return false. A false return code means that an internal error has corrupted the module state.
Select and register in an internal state a queue identifier; invoke
mbfl_at_validate_queue_letterto validate the selection.
Schedule command in the currently selected queue; the script will be executed at time.
If no error occurs: print to stdout the identifier of the scheduled job; the identifier can be used as argument to
mbfl_at_drop.The at command outputs some text (in which the job is embedded) on its stderr channel, so this function redirects stderr to stdout to return the value; this operation conflicts with the use of the “show program” feature ofmbfl_program_exec.command must be a string representing the invocation of an external executable program: it is sent unchanged to the at command. time is the argument to the at command, see the manual page for its description.
Remove a job; the identifier of a job is unique in all the queues, so this function is not affected by the currently selected queue.
Print all the job identifiers in the currently selected queue.
Print all the job descriptions in the currently selected queue.
MBFL provides an interface to the trap builtin that allows the
execution of more than one function when a signal is received; this may
sound useless, but that is it.
Convert sigspec to the corresponding signal number, then print the number.
Append handler to the list of functions that are executed whenever sigspec is received.
Invoke all the handlers registered for signum. This function is not meant to be used during normal scripts execution, but it may be useful to debug a script.
Return true if the character at position in string is quoted; else return false. A character is considered quoted if it is preceded by an odd number of backslashes (
\). position is a zero–based index.
Return true if the character at position in string is equal to char and is not quoted (according to
mbfl_string_is_quoted_char); else return false. position is a zero–based index.
Print string with quoted characters. All the occurrences of the backslash character,
\, are substituted with a quoted backslash,\\. Return true.
Select a character from a string. Echo to stdout the selected character. If the index is out of range: the empty string is echoed to stdout, that is: a newline is echoed to stdout.
Search characters in a string. Arguments: string, the target string; char, the character to look for; begin, optional, the index of the character in the target string from which the search begins (defaults to zero).
Print an integer representing the index of the first occurrence of char in string. If the character is not found: nothing is sent to stdout.
Search characters in a string starting from the end. Arguments: string, the target string; char, the character to look for; begin, optional, the index of the character in the target string from which the search begins (defaults to zero).
Print an integer representing the index of the last occurrence of char in string. If the character is not found: nothing is sent to stdout.
Extract a range of characters from a string. Arguments: string, the source string; begin, the index of the first character in the range; end, optional, the index of the character next to the last in the range, this character is not extracted. end defaults to the last character in the string; if equal to
end: the end of the range is the end of the string. Echo to stdout the selected range of characters.
Return true if the substring starting at position in string is equal to pattern; else return false. If position plus the length of pattern is greater than the length of string: the return value is false, always.
Split a string into characters. Fill an array named
SPLITFIELDwith the characters from the string; the number of elements in the array is stored in a variable namedSPLITCOUNT. BothSPLITFIELDandSPLITCOUNTcan be declaredlocalin the scope of the caller.The difference between this function and using
${STRING:$i:1}, is that this function detects backslash characters,\, and treats them as part of the following character. So, for example, the sequence\nis treated as a single char.
Example of usage for mbfl_string_chars:
string="abcde\nfghilm"
mbfl_string_chars "${string}"
# Now:
# "${#string}" = $SPLITCOUNT
# a = "${SPLITFIELD[0]}"
# b = "${SPLITFIELD[1]}"
# c = "${SPLITFIELD[2]}"
# d = "${SPLITFIELD[3]}"
# e = "${SPLITFIELD[4]}"
# \n = "${SPLITFIELD[5]}"
# f = "${SPLITFIELD[6]}"
# g = "${SPLITFIELD[7]}"
# h = "${SPLITFIELD[8]}"
# i = "${SPLITFIELD[9]}"
# l = "${SPLITFIELD[10]}"
# m = "${SPLITFIELD[11]}"
Split string into fields using separator. Fill an array named
SPLITFIELDwith the characters from the string; the number of elements in the array is stored in a variable namedSPLITCOUNT. BothSPLITFIELDandSPLITCOUNTcan be declaredlocalin the scope of the caller.
Output string with all the occurrences of lower case ASCII characters (no accents) turned into upper case.
Output string with all the occurrences of upper case ASCII characters (no accents) turned into lower case.
Return true if:
mbfl_string_is_alpha_char char || \ mbfl_string_is_digit_char char
Return true if char is none of the characters: ,
\n,\r,\f,\t. char is meant to be the unquoted version of the non–blank characters, the one obtained with:$'char'
Return true if
mbfl_string_is_alnum_charreturns true when applied to char or char is an underscore,_.
Return true if the associated char function returns true for each character in string. As additional constraint:
mbfl_string_is_namereturns false ifmbfl_string_is_digitreturns true when applied to the first character of string.
Replace all the occurrences of pattern in string with subst, then print the result. If not used, subst defaults to the empty string.
Make use of
printfto format the string format with the additional arguments, then store the result in varname: If this name is local in the scope of the caller, this has the effect of filling the variable in that scope.
Skip all the characters in a string equal to char. varname is the name of a variable in the scope of the caller: Its value is the offset of the first character to test in string. The offset is incremented until a char different from char is found, then the value of varname is updated to the position of the different char. If the initial value of the offset corresponds to a char equal to char, the variable is left untouched. Return true.
Print the question string on the standard output and wait for the user to type
yesornoin the standard input. Return true if the user has typedyes, false if the user has typedno.The optional parameter progname is used as prefix for the prompt; if not given: It defaults to the value of
script_PROGNAME. Required user defined variables.
Declare the usage of the external program stty, which is used by
mbfl_dialog_ask_passwordto turn of password echoing on the terminal.
Print prompt followed by a colon and a space, then reads a password from the terminal. Print the password.
Manipulating colon variables, for the use of the following functions.
Search the array
mbfl_FIELDSfor a value equal to element. If it is found: Print the index and return true; else print nothing and return false.
mbfl_FIELDSmust be filled with elements having subsequent indexes starting at zero.
A wrapper for
mbfl_variable_find_in_arraythat does not print anything.
Take varname's value, a colon separated list of string, and store each string in the array
mbfl_FIELDS, starting with a base index of zero.Example:
VAR=a:b:c:d:e declare -a mbfl_FIELDS mbfl_variable_colon_variable_to_array VAR echo ${#mbfl_FIELDS[*]} -| 5 echo "${mbfl_FIELDS[0]}" -| a echo "${mbfl_FIELDS[1]}" -| b echo "${mbfl_FIELDS[2]}" -| c echo "${mbfl_FIELDS[3]}" -| d echo "${mbfl_FIELDS[4]}" -| e
Store each value from the array
mbfl_FIELDS(with base index zero) in varname as a colon separated list of strings.Example:
declare -a mbfl_FIELDS=(a b c d e) mbfl_variable_array_to_colon_variable VAR echo $VAR -| a:b:c:d:e
Take varname's value, a colon separated list of string, and remove duplicates. Reset varname to the result.
Declare the intention to use the programs required by this module. Declaring the intention to use a program.
Required programs are: grep, cut.
Convert the numerical user id to the user name found in the /etc/passwd file.
Convert the symbolic user name to the numerical identifier found in the /etc/passwd file.
Convert three chars representing file permissions in a single octal digit.
Convert a single octal digit representing file permissions into three chars.
MBFL declares a function to drive the execution of the script; its purpose is to make use of the other modules to reduce the size of scripts depending on MBFL. All the code blocks in the script, with the exception of global variables declaration, should be enclosed in functions.
The invocation to this function must be the last line of code in the script. It does the following:
- Register the value of the variable
script_PROGNAMEin the message module using the functionmbfl_message_set_progname.- Invoke
mbfl_main_create_exit_aliases. Declaring exit codes.- If it exists: Invoke the function
script_before_parsing_options.- Parse command line options with
mbfl_getopts_parse.- If it exists: Invoke the function
script_after_parsing_options.- Invoke the function whose name is stored in the global variable
mbfl_main_SCRIPT_FUNCTION, if it exists, with no arguments; if its return value is non–zero: Exit the script with the same code. The default value ismain.- Exit the script with the return code of the action function or zero.
If funcname is the name of an existing function: It is invoked with no arguments; the return value is the one of the function. The existence test is performed with:
type -t FUNCNAME = function
Select the main function storing funcname into
mbfl_main_SCRIPT_FUNCTION.
Global variable that holds the name of the custom main script function.
Some functions and global variables are provided to declare script's exit codes.
Declare an exit code with value code and identifier name.
For each of the codes declared with
mbfl_main_declare_exit_code: Create a function for theexitcommand using the numerical code. For example, if a code is declared as:mbfl_main_declare_exit_code 4 unexistent_filea function is created with:
function exit_because_unexistent_file () { exit 4; }the name of the function is the string
exit_because_followed by the exit code name. The function may be used in the script to exit the process.
By default the exit code 0 is associated to the name
success and the exit code 1 is associated to the name
failure; so the following functions exist.
MBFL comes with a little library of functions that may be used to build test suites; its aim is at building tests for Bash functions, commands and scripts.
The ideas at the base of this library are taken from the tcltest
package distributed with the TCL core 1;
this package had contributions from the following people/entities: Sun
Microsystems, Inc.; Scriptics Corporation; Ajuba Solutions; Don Porter,
NIST; probably many many others.
The library tries to do as much as possible using functions and aliases, not variables; this is an attempt to let the user redefine functions to his taste.
A useful way to organise a test suite is to split it into a set of files: one for each module to be tested.
The file libmbfltest.sh must be sourced at the beginning of each
test file. This means that the variables that you set may interfere
with the ones in the library; this should not happen because the test
library prefixes variable names with mbfl_ or dotest_, but
one exception is TMPDIR: do not set it in your script, use
dotest-echo-tmpdir to access that value. Handling files in tests.
To understand how the library works lets examine a bare bones example.
The function dotest should be invoked at the end of each module
in the test suite; each module should define functions starting with the
same prefix. A module should be stored in a file, and should look like
the following:
# mymodule.test --
source libmbfltest.sh
source module.sh
function module-featureA-1.1 () { ... }
function module-featureA-1.2 () { ... }
function module-featureA-2.1 () { ... }
function module-featureB-1.1 () { ... }
function module-featureB-1.2 () { ... }
dotest module-
### end of file
the file should be executed with:
$ bash mymodule.test
To test just "feature A":
$ TESTMATCH=module-featureA bash mymodule.test
Remember that the source builtin will look for files in the
directories selected by the PATH environment variables, so we may
want to do:
$ PATH=path/to/modules:${PATH} \
TESTMATCH=module-featureA bash mymodule.test
It is better to put such stuff in a Makefile, with GNU Make:
srcdir = ...
builddir = ...
BASH_PROGRAM = bash
MODULES = moduleA moduleB
testdir = $(srcdir)/tests
test_FILES = $(foreach f,$(MODULES),$(testdir)/$(f).test)
test_ENV = PATH=$(builddir):$(testdir):$(PATH) \
TESTMATCH=$(TESTMATCH)
test_CMD = $(test_ENV) $(BASH_PROGRAM)
.PHONY: test-modules
test-modules:
@$(foreach f,$(test_FILES),$(test_CMD) $(f);)
MBFL comes with a script that can be used to handle the execution of tests; it is called mbfltest.sh. Synopsis:
mbfltest.sh [options] TESTFILE ...
supported options are all the MBFL generic ones (Predefined options) and additionally:
--startdotest-set-report-start.
--enddotest-set-report-success.
--match=VALUE--directory=VALUE--library=VALUEWhen running tests with the script: in the test modules we can omit the sourcing of MBFL and the MBFL test library, mbfltest.sh does this before sourcing the test module. Each test module is evaluated in a bash subprocess, so: there is no interference between modules; each module has to do its own initialisation and finalisation.
With GNU Make we can do:
MBFLTEST = mbfltest.sh
MBFLTEST_FLAGS = --end
ifneq (,$(TESTMATCH))
MBFLTEST_FLAGS += --match=$(TESTMATCH)
endif
srcdir = ...
testdir = $(srcdir)/tests
TESTNAME = *
TESTFILES = $(wildcard $(testdir)/$(TESTNAME).test)
.PHONY: test tests
ifneq ($(strip $(TESTFILES)),)
test tests:
$(MBFLTEST) $(MBFLTEST_FLAGS) $(TESTFILES)
endif
Set or unset verbose execution. If verbose mode is on: some commands output messages on stderr describing what is going on. Examples: files and directories creation/removal.
Set or unset test execution. If test mode is on: external commands (like rm and mkdir) are not executed, the command line is sent to stderr. Test mode is meant to be used to debug the test library functions.
Set or unset printing a message upon starting a function.
Return true if start function reporting is on; otherwise return false.
Set or unset printing a message when a function execution succeeds. Failed tests always cause a message to be printed.
Return true if success function reporting is on; otherwise return false.
Run all the functions matching pattern. Usually pattern is the first part of the name of the functions to be executed; the function names are selected with the following code:
compgen -A function patternThere's no constraint on function names, but they must be one–word names.
Before running a test function: the current process working directory is saved, and it is restored after the execution is terminated.
The return value of the test functions is used as result of the test: true, the test succeeded; false, the test failed. Remembering that the return value of a function is the return value of its last executed command, the functions
dotest-equalanddotest-output, and of course the test command, may be used to return the correct value.
Messages are printed before and after the execution of each function,
according to the mode selected with: dotest-set-report-success,
dotest-set-report-start, ... Configuring the package
The following environment variables will influence the behaviour of
dotest.
If
yes: It is equivalent to invokingdotest-set-report-start. Ifno: It is equivalent to invokingdotest-unset-report-start.
If
yes: It is equivalent to invokingdotest-set-report-success. Ifno: It is equivalent to invokingdotest-unset-report-success.
Compare the two parameters and return true if they are equal; return false otherwise. In the latter case print a message showing the expected value and the wrong one. Must be used as last command in a function, so that its return value is equal to that of the function.
Example:
function my-func () {
echo $(($1 + $2))
}
function mytest-1.1 () {
dotest-result 5 `my-func 2 3`
}
dotest mytest-
another example:
function my-func () {
echo $(($1 + $2))
}
function mytest-1.1 () {
dotest-result 5 `my-func 2 3` && \
dotest-result 5 `my-func 1 4` && \
dotest-result 5 `my-func 3 2` && \
}
dotest mytest-
Read all the available lines from stdin accumulating them into a local variable, separated by
\n; then compare the input with string, or the empty string if string is not present, and return true if they are equal, false otherwise.
Example of test for a function that echoes its three parameters:
function my-lib-function () {
echo $1 $2 $3
}
function mytest-1.1 () {
my-lib-function a b c | dotest-output "a b c"
}
dotest mytest
Example of test for a function that is supposed to print nothing:
function my-lib-function () {
test "$1" != "$2" && echo error
}
function mytest-1.1 () {
my-lib-function a a | dotest-output
}
dotest mytest
Print the parameters on stderr.
dotest-debugprints some*to make the message more visible.
In this section are described functions to be used to create temporary
files; it is a common task to write scripts to manipulate files and
directories. All the files should be created under a temporary
directory that must be removed after each test function is invoked; the
library automatically invokes dotest-clean-files when exiting
(using trap), but it is safer to invoke it at the end of each
function that creates files.
Change the working directory. This is just a wrapper for cd; if verbose mode is on: print a message.
Create directory under the temporary directory; directory must be a relative pathname (that is: it must not begin with a slash).
The optional prefix is a relative pathname that is prepended to directory: it is useful to prepend the name of a parent directory.
Print to stdout the full pathname of the directory.
Print the value of the temporary directory in which all the files and directories will be created. The value is prefixed with the value of the environment variable TMPDIR, or /tmp if not set.
Create the temporary directory. This is automatically invoked by
dotest-mkfilebefore creating files;dotest-mkdircreates the temporary directory automatically by using the --parents option of mkdir.
Create an empty file. The optional prefix is a relative pathname that is prepended to pathname: It is useful to prepend the name of a parent directory. Print to stdout the full pathname of the file.
Remove the temporary directory and all its children. Should be invoked at the end of each function that creates temporary files or directories.
Return the value of the last command executed before the invocation, that way it can be used right after
dotest-outputanddotest-equalwithout loosing the return value of the function.
Test that file exists: If true returns with code zero; else print error_message, invoke
dotest-clean-filesand return with code one.
Test that file does not exist: If true return with code zero; else print error_message, invoke
dotest-clean-filesand return with code one.
Examples of usage of dotest-clean-files:
function mytest-1.1 () {
local dir=$(dotest-mkdir a/b)
local result=
...
result=...
dotest-equal 123 $result
dotest-clean-files
}
function mytest-1.2 () {
local dir=$(dotest-mkfile file.ext)
local result=
...
result=...
dotest-equal 123 $result
dotest-clean-files
}
dotest mytest-
In this appendix we review some example scripts that can be used to send email from a Bash script. All the scripts are in the MBFL distribution under the examples directory.
First we examine plain scripts (making no use of MBFL) to understand the basics of how to handle the SMTP protocol and how to “talk” to a process in background.
Then we see the documentation of a complex script, sendmail-mbfl.sh, which can be used to send mail with plain or encrypted connections.
Finally we see how to use the GNU Emacs interface to the script, sendmail-mbfl.el. sendmail script emacs.
Here we discuss how to programmatically compose a minimal email message to be used in testing email scripts. Basically a message should look like this:
Sender: marco@localhost
From: marco@localhost
To: root@localhost
Subject: proof from sendmail-plain.sh
Message-ID: <15704-6692-23464@this.hostname>
Date: Tue, 28 Apr 2009 06:16:01 +0200
This is a text proof from the sendmail-plain.sh script.
--
Marco
We have to remember that the SMTP server receiving the message may
rewrite the addresses, for example: replacing localhost with the
fully qualified local host name (the output of the command
hostname --fqdn); so, when reading the delivered message, we
do not have to be surprised to find changed addresses.
We want to notice the following:
LOCAL_HOSTNAME=$(hostname --fqdn)
do not confuse this value with the host name of the SMTP server!
Message-ID header must be enclosed in angular
parentheses, and it must contain an address–like string with random
characters in the name part. It can be generated with:
MESSAGE_ID=$(printf '%d-%d-%d@%s' \
$RANDOM $RANDOM $RANDOM "$LOCAL_HOSTNAME")
or with:
MESSAGE_ID=$(printf '%s@%s' \
$({ IFS= read -n 15 line </dev/random \
echo "$line" ; } | \
md5sum --binary | cut -f1 -d' ') \
"$LOCAL_HOSTNAME")
Date header must be the current date in a
specified format. We can generate it using the GNU Date program like
this:
DATE=$(date --rfc-2822) || exit 2
-- (dash, dash, white
space). When composing the message we have to be careful to use
commands that do not drop white spaces.
In the end, we can use the following chunk of code to compose an email message:
PROGNAME=${0##*/}
FROM_ADDRESS=marco@localhost
TO_ADDRESS=root@localhost
function print_message () {
local LOCAL_HOSTNAME DATE MESSAGE_ID MESSAGE
LOCAL_HOSTNAME=$(hostname --fqdn) || exit 2
DATE=$(date --rfc-2822) || exit 2
MESSAGE_ID=$(printf '%d-%d-%d@%s' \
$RANDOM $RANDOM $RANDOM "$LOCAL_HOSTNAME")
MESSAGE="Sender: $FROM_ADDRESS
From: $FROM_ADDRESS
To: $TO_ADDRESS
Subject: proof from $PROGNAME
Message-ID: <$MESSAGE_ID>
Date: $DATE
This is a text proof from the $PROGNAME script.
--\x20
Marco
"
printf "$MESSAGE"
}
notice that to put the required single white space character in the
text/signature separator we use the escape sequence \x20 (where
20 is the hexadecimal value of the white space character in the ASCII
encoding) and print the message with printf, which expands the
escape sequences.
When sending the message to the SMTP server we have to:
\r\n).
So we can use an equivalent of the following chunk of code, assuming
3 is the file descriptor connected to the remote SMTP server:
print_message | while IFS= read line
do printf '%s\r\n' "$line" >&3
done
notice that read is executed in an environment in which
IFS is set to the empty string, this is to prevent unwanted
modification of the message text. read splits the string it
reads into words according to the current value of IFS, and this
may lead to mutation of the input string; word splitting happens when
there is a single output variable, too. To prevent word splitting, we
set IFS to the empty string.
The script below can be found in examples/sendmail-plain.sh. It just sends a hard–coded email message, from a hard–coded address to a hard–coded address. It makes no use of MBFL.
Drive the script controlling the SMTP protocol. It should be obvious what it does once we understand the following functions.
Open a connection to the SMTP server using a fake device that Bash gives us as interface to the network. For the
localhost, it ends up being:/dev/tcp/localhost/25where 25 is the TCP port which is officially assigned to the SMTP service. To open the connection we use the idiom:
exec 3<>/dev/tcp/localhost/25which means: open a read and write connection to the selected hostname, using file descriptor number 3. There is nothing special in number 3, it is just the first free file descriptor number after 0 (standard input), 1 (standard output) and 2 (standard error).
The line:
trap 'exec 3<&-' EXITmeans: close file descriptor 3 whenever the script terminates. This is redundant in such a simple script, it is there for completeness.
Send a string to the SMTP server. Use
printfto format the string pattern with the optional arguments, then write the resulting string to file descriptor 3. The string written out is terminated with the sequence\r\nas mandated by the SMTP protocol.
Read an email message from stdin line by line (newline terminator), and rewrite it to file descriptor 3 terminating each line with the sequence carriage return/line feed. With the exception of the terminating sequence, the lines are left unchanged.
Read a line (a sequence of characters up until the first
\n) from file descriptor 3. The line is interpreted as a message from the SMTP server: the first three characters are a numeric code. If the code is different from expected_code, raise an error.
#! /bin/bash
#
# Part of: Marco's Bash Functions Library
# Contents: example script to send email
# Date: Thu Apr 23, 2009
#
# Abstract
#
# This script just sends a hardcoded email message from
# a hardcoded address to a hardcoded address. It makes
# no use of MBFL.
#
# The purpose of this script is to understand how to
# handle the SMTP protocol.
#
# Copyright (c) 2009 Marco Maggi <marcomaggi@gna.org>
#
# This program is free software: you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, either
# version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public
# License along with this program. If not, see
# <http://www.gnu.org/licenses/>.
#
PROGNAME=${0##*/}
function main () {
local HOSTNAME=localhost
local SMTP_PORT=25
local FROM_ADDRESS=marco@localhost
local TO_ADDRESS=root@localhost
local LOGGING_TO_STDERR=yes
open_session "$HOSTNAME"
recv 220
send 'HELO %s' 127.0.0.1
recv 250
send 'MAIL FROM:<%s>' "$FROM_ADDRESS"
recv 250
send 'RCPT TO:<%s>' "$TO_ADDRESS"
recv 250
send %s DATA
recv 354
print_message | read_and_send_message
send %s .
recv 250
send %s QUIT
recv 221
}
function print_message () {
local LOCAL_HOSTNAME DATE MESSAGE_ID MESSAGE
LOCAL_HOSTNAME=$(hostname --fqdn) || exit 2
DATE=$(date --rfc-2822) || exit 2
MESSAGE_ID=$(printf '%d-%d-%d@%s' \
$RANDOM $RANDOM $RANDOM "$LOCAL_HOSTNAME")
MESSAGE="Sender: $FROM_ADDRESS
From: $FROM_ADDRESS
To: $TO_ADDRESS
Subject: proof from $PROGNAME
Message-ID: <$MESSAGE_ID>
Date: $DATE
This is a text proof from the $PROGNAME script.
--\x20
Marco
"
printf "$MESSAGE"
}
function open_session () {
local HOSTNAME=${1:?}
local DEVICE=$(printf '/dev/tcp/%s/%d' "$HOSTNAME" $SMTP_PORT)
exec 3<>"$DEVICE"
trap 'exec 3<&-' EXIT
}
function recv () {
local EXPECTED_CODE=${1:?}
local line=
IFS= read -t 5 line <&3
test 127 -lt $? && {
printf '%s: connection timed out\n' "$PROGNAME" >&2
exit 2
}
test "$LOGGING_TO_STDERR" = yes && \
printf '%s log: recv: %s\n' "$PROGNAME" "$line"
test "${line:0:3}" = "$EXPECTED_CODE" || {
send %s QUIT
# It is cleaner to wait for the reply from the
# server.
IFS= read -t 5 line <&3
test 127 -lt $? && {
printf '%s: connection timed out\n' "$PROGNAME" >&2
exit 2
}
test "$LOGGING_TO_STDERR" = yes && \
printf '%s log: recv: %s\n' "$PROGNAME" "$line"
exit 2
}
}
function send () {
local pattern=${1:?}
shift
local line=$(printf "$pattern" "$@")
printf '%s\r\n' "$line" >&3
test "$LOGGING_TO_STDERR" = yes && \
printf '%s log: sent: %s\n' "$PROGNAME" "$line"
}
function read_and_send_message () {
local line
local -i count=0
while IFS= read line
do
printf '%s\r\n' "$line" >&3
let ++count
done
test "$LOGGING_TO_STDERR" = yes && \
printf '%s log: sent message (%d lines)\n' "$PROGNAME" $count
}
main
### end of file
The script below can be found in examples/sendmail-connector.sh. It just sends a hard–coded email message, from a hard–coded address to a hard–coded address. It makes no use of MBFL.
Bash version 4 introduced the new keyword coproc, which can be
used to spawn processes in background and talk to them via pipes. This
keyword in not used in this appendix.
The purpose of the script is to understand how to send a message through
a process in background. It does the same things of the example
described in Just send an email message. The main
difference is that the single function open_session is replaced
by the two functions open_session and connector.
What is important to understand, is how open_session runs
connector in background and sets up two file descriptors to talk
to it. In the real world we never use this technique with a function;
this example script makes use of connector as a replacement for
an external program that can establish sophisticated connections to
remote hosts, for example using the TLS/SSL protocols.
Open a connection to the SMTP server spawning a background process represented by the
connectorfunction. It makes use of two FIFOs (First In, First Out).If we were to do it from a C language program: we would use the
pipe()system function to create two pipes connecting script's process to the background process.--------- out pipe ----------- socket -------- | script |--------->| connector |<======>| SMTP | | process |<---------| process | | server | --------- in pipe ----------- --------Bash has no way to create a pipe using the
pipe()system function (up until version 4), so we use two FIFO channels created by the mkfifo program:: ${TMPDIR:=/tmp} local INFIFO=${TMPDIR}/in.$$ local OUFIFO=${TMPDIR}/out.$$ mkfifo --mode=0600 $INFIFO $OUFIFOthe script will use
INFIFOto read characters fromconnector, andOUFIFOto send characters toconnector.--------- OUFIFO ----------- socket -------- | script |-------->| connector |<======>| SMTP | | process |<--------| process | | server | --------- INFIFO ----------- --------Once the FIFOs exist on the file system, we run
connectorin background, connecting its standard input and output to the FIFOs:connector $HOSTNAME $SMTP_PORT <$OUFIFO >$INFIFO &be careful in selecting the redirections. Notice that, in this simple example, we ignore errors running
connector.Now we open file descriptors connecting them to the FIFOs:
exec 3<>$INFIFO 4>$OUFIFOthe script will use file descriptor 3 to read characters from
connector, and file descriptor 4 to send characters toconnector. We open the input FIFO for both reading and writing, elseexecwill block waiting for the first char.We have connected both the ends of both the FIFOs, so we can remove them from the file system:
rm $INFIFO $OUFIFOthe FIFOs will continue to exist in the OS kernel until the file descriptors are closed.
Finally we register a clean up handler that closes the descriptors:
trap 'exec 3<&- 4>&-' EXIT
Establish a connection to the SMTP server at hostname. It is not important here to fully understand how this function works; suffice it to say that it reads lines from stdin, and echoes them to the server; it reads lines from the server, and echoes them to stdout.
#! /bin/bash
#
# Part of: Marco's Bash Functions Library
# Contents: example script to send email using bg process
# Date: Thu Apr 23, 2009
#
# Abstract
#
# This script just sends a hardcoded email message from
# a hardcoded address to a hardcoded address. It makes
# no use of MBFL.
#
# The purpose of this script is to understand how to
# send a message through a process in background.
#
# Copyright (c) 2009 Marco Maggi <marcomaggi@gna.org>
#
# This program is free software: you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, either
# version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public
# License along with this program. If not, see
# <http://www.gnu.org/licenses/>.
#
PROGNAME=${0##*/}
: ${TMPDIR:=/tmp}
function main () {
local HOSTNAME=localhost
local SMTP_PORT=25
local FROM_ADDRESS=marco@localhost
local TO_ADDRESS=root@localhost
local LOGGING_TO_STDERR=yes
open_session "$HOSTNAME"
recv 220
send 'HELO %s' 127.0.0.1
recv 250
send 'MAIL FROM:<%s>' "$FROM_ADDRESS"
recv 250
send 'RCPT TO:<%s>' "$TO_ADDRESS"
recv 250
send %s DATA
recv 354
print_message | read_and_send_message
send %s .
recv 250
send %s QUIT
recv 221
}
function print_message () {
local LOCAL_HOSTNAME DATE MESSAGE_ID MESSAGE
LOCAL_HOSTNAME=$(hostname --fqdn) || exit 2
DATE=$(date --rfc-2822) || exit 2
MESSAGE_ID=$(printf '%d-%d-%d@%s' \
$RANDOM $RANDOM $RANDOM "$LOCAL_HOSTNAME")
MESSAGE="Sender: $FROM_ADDRESS
From: $FROM_ADDRESS
To: $TO_ADDRESS
Subject: proof from $PROGNAME
Message-ID: <$MESSAGE_ID>
Date: $DATE
This is a text proof from the $PROGNAME script.
--\x20
Marco
"
printf "$MESSAGE"
}
function open_session () {
local HOSTNAME=${1:?}
local INFIFO=${TMPDIR}/in.$$
local OUFIFO=${TMPDIR}/out.$$
# Bash has no operation equivalent to the C level
# "pipe()" function, so we have to use FIFOs.
mkfifo --mode=0600 $INFIFO $OUFIFO
connector "$HOSTNAME" <$OUFIFO >$INFIFO &
# Open the input FIFO for both reading and writing, else
# "exec" will block waiting for the first char.
exec 3<>$INFIFO 4>$OUFIFO
# We have connected both the ends of both the FIFOs, so
# we can remove them from the file system: the FIFOs
# will continue to exist until the file descriptors are
# closed.
rm $INFIFO $OUFIFO
trap 'exec 3<&- 4>&-' EXIT
}
function recv () {
local EXPECTED_CODE=${1:?}
local line=
IFS= read line <&3
test "$LOGGING_TO_STDERR" = yes && \
printf '%s log: recv: %s\n' "$PROGNAME" "$line"
if test "${line:0:3}" != "$EXPECTED_CODE"
then
send %s QUIT
# It is cleaner to wait for the reply from the
# server.
IFS= read line <&3
test "$LOGGING_TO_STDERR" = yes && \
printf '%s log: recv: %s\n' "$PROGNAME" "$line"
exit 2
fi
}
function send () {
local pattern=${1:?}
shift
local line=$(printf "$pattern" "$@")
printf '%s\r\n' "$line" >&4
test "$LOGGING_TO_STDERR" = yes && \
printf '%s log: sent: %s\n' "$PROGNAME" "$line"
}
function read_and_send_message () {
local line
local -i count=0
while IFS= read line
do
printf '%s\r\n' "$line" >&4
let ++count
done
test "$LOGGING_TO_STDERR" = yes && \
printf '%s log: sent message (%d lines)\n' "$PROGNAME" $count
}
function connector () {
local HOSTNAME=${1:?} query= answer= line=
local DEVICE=$(printf '/dev/tcp/%s/%d' "$HOSTNAME" $SMTP_PORT)
exec 3<>"$DEVICE"
# Read the greetings from the server, echo them to the
# client.
IFS= read -t 5 answer <&3
test 127 -lt $? && {
printf '%s: connection timed out\n' "$PROGNAME" >&2
exit 2
}
printf '%s\n' "$answer"
# Read the query from the client, echo it to the server.
while read query
do
printf '%s\r\n' "$query" >&3
# Read the answer from the server, echo it to the
# client.
IFS= read -t 5 answer <&3
test 127 -lt $? && {
printf '%s: connection timed out\n' "$PROGNAME" >&2
exit 2
}
printf '%s\n' "$answer"
# Test special queries.
test "$query" = QUIT$'\r' && {
IFS= read -t 5 answer <&3
test 127 -lt $? && {
printf '%s: connection timed out\n' "$PROGNAME" >&2
exit 2
}
printf '%s\n' "$answer"
exit
}
test "$query" = DATA$'\r' && {
# Read data lines from the client, echo them to
# the server up until ".\r" is read.
while IFS= read line
do
printf '%s\n' "$line" >&3
test "${line:0:2}" = .$'\r' && break
done
# Read the answer to data from the server, echo
# it to the client.
IFS= read -t 5 answer <&3
test 127 -lt $? && {
printf '%s: connection timed out\n' "$PROGNAME" >&2
exit 2
}
printf '%s\n' "$answer"
}
done
# We should never come here.
exit 1
}
main
### end of file
GNU TLS is a library implementing the TLS protocol; it can be used to establish encrypted and authenticated connections to a remote host. The SMTP protocol has extensions to allow usage of a TLS layer.
A GNU TLS installation comes with a command line test program, gnutls-cli, that can be used to establish an encrypted connection by a shell script. This command can be used as the “connector” modeled in Send email through a process in background. It has a manual page, which we may want to read.
When handling an encrypted connection we have to know in advance how the remote SMTP server behaves. Let's see first the simpler example, using the a human driven interactive session; then we will describe a more complex interaction.
Notice that gnutls-cli has a --crlf option that will cause all the lines sent to the server to be terminated by a carriage return/line feed sequence (\r\nor\x0d\x0a). If we write a script that terminates by itself the lines with this sequence, for example:printf 'ehlo localhost.localdomain\r\n'we must avoid this option, else SMTP protocol violation errors may occur. However, if we try a hand–driven interactive session, we want to use this option to send protocol–compliant lines.
We use as example the server relay.poste.it, port 465.
You have to have an account there to use it; do not bomb this server
with fake connections. This server requests us to build the encrypted
bridge immediately after the connection has been established, without
waiting for any line of greetings from the server.
So, we start the connector like this:
$ gnutls-cli --port 465 relay.poste.it
if the connection succeeds: gnutls-cli prints a lot of message
lines on its standard output explaining what is going on; at last comes
the line of greetings from the server, which begins with code 220.
The server supports the AUTH LOGIN authentication mechanism,
which requires the base64 encoding of the user name and password; we can
perform it with the external program base64 (which comes with
GNU Coreutils) like this:
ENCODED_USERNAME=$(echo -n 'the-user-name' | base64)
ENCODED_PASSWORD=$(echo -n 'the-pass-word' | base64)
GNU Emacs users can do it with:
(setq my-usr (base64-encode-string "the-user-name"))
(setq my-pwd (base64-encode-string "the-pass-word"))
The authentication dialogue goes like this:
AUTH LOGIN, to start the authentication.
334 VXNlcm5hbWU6 which is the request for the
username. The string VXNlcm5hbWU6 is the base64 encoding of the
string Username: (without trailing newline); we can verify this
with:
$ echo -n Username: | base64
or in the Emacs' scratch buffer:
(base64-encode-string "Username:")
334 UGFzc3dvcmQ6 (this should happen even if the username is
unknown to the server). The string UGFzc3dvcmQ6 is the base64
encoding of the string Password: (without ending newline); we can
verify this with:
$ echo -n Password: | base64
or in the Emacs' scratch buffer:
(base64-encode-string "Password:")
235.
Beware that if we are not quick to send the encoded password after the encoded user name, the server may reset the authentication process as if we sent a wrong user name.
So we can do the SMTP dialogue reported below by hand (which is an
edited log of a session under Emacs' eshell); lines starting with
recv> are the ones received from the server, lines starting with
send> are the ones we send to the server, the ellipses ...
are replacements for server text we are not interested in.
$ gnutls-cli --crlf --port 465 relay.poste.it
recv> 220 ... ESMTP Service ...
send> ehlo localhost.localdomain
recv> 250-...
recv> 250-DSN
recv> 250-8BITMIME
recv> 250-PIPELINING
recv> 250-HELP
recv> 250-AUTH=LOGIN
recv> 250-AUTH LOGIN CRAM-MD5 DIGEST-MD5 PLAIN
recv> 250-DELIVERBY 300
recv> 250 SIZE
send> auth login
recv> 334 VXNlcm5hbWU6
send> <the-base64-username>
recv> 334 UGFzc3dvcmQ6
send> <the-base64-password>
recv> 235 login authentication successful
send> mail from:<from-address@poste.it>
recv> 250 MAIL FROM:<from-address@poste.it> OK
send> rcpt to:<to-address@other-host.it>
recv> 250 RCPT TO:<to-address@other-host.it> OK
send> data
recv> 354 Start mail input; end with <CRLF>.<CRLF>
send> From: <from-address@poste.it>
send> To: <to-address@other-host.it>
send> Subject: interactive attempt
send>
send> Text for interactive attempt.
send> --
send> Marco
send> .
recv> 250 ... Mail accepted
send> quit
recv> 221 ... QUIT
recv> - Peer has closed the GNUTLS connection
We use as example the server smtp.gmail.com, port 587.
You have to have an account there to use it; do not bomb this server
with fake connections. This server requests us to start an ESMTP
dialogue, then issue the STARTTLS command and build the encrypted
bridge; once the bridge is set up, we restart an ESMTP dialogue
and do the authentication and the message delivery.
We start the connector like this:
$ gnutls-cli --starttls --port 587 smtp.gmail.com
if the connection succeeds: gnutls-cli prints message lines on
its standard output explaining what is going on; at last comes the line
of greetings from the server, which begins with code 220.
The --starttls option tells gnutls-cli not
to build the encrypted bridge immediately; rather, it waits for a
SIGALRM signal, which we must deliver to it when we are ready.
The quickest way to send such a signal, when there is only one
gnutls-cli process running, is:
$ kill -SIGALRM $(/sbin/pidof gnutls-cli)
beware that pidof may be installed in other places on your system.
The server supports the AUTH PLAIN authentication mechanism,
which requires the base64 encoding of the user name and password stored
in a special record; we can do it with the external program
base64 (which comes with GNU Coreutils) like this:
SECRETS=$(printf "\x00${LOGIN_NAME}\x00${PASSWORD}" | base64)
GNU Emacs users can do it with:
(setq my-auth (base64-encode-string
(format "%c%s%c%s" 0 "the-user-name" 0 "the-pass-word")))
The authentication dialogue goes like this:
AUTH PLAIN followed by the encoded credentials.
235.
So, we can do the SMTP dialogue reported below by hand (which is an
edited log of a session under Emacs' eshell); lines starting with
recv> are the ones received from the server, lines starting with
send> are the ones we send to the server, the ellipses ...
are replacements for server text we are not interested in.
$ gnutls-cli --crlf --starttls --port 587 smtp.gmail.com
recv> 220 ... ESMTP ...
send> ehlo localhost.localdomain
recv> 250-...
recv> 250-SIZE 35651584
recv> 250-8BITMIME
recv> 250-STARTTLS
send> 250-ENHANCEDSTATUSCODES
recv> 250 PIPELINING
send> starttls
recv> 220 2.0.0 Ready to start TLS
=== here we deliver SIGALRM to the gnutls-cli process
recv> *** Starting TLS handshake
recv> - Certificate type: X.509
recv> - Got a certificate list of 1 certificates.
recv>
recv> - Certificate[0] info:
recv> # The hostname in the certificate matches 'smtp.gmail.com'.
recv> # valid since: ..
recv> # expires at: ...
recv> # fingerprint: ...
recv> # Subject's DN: ...
recv> # Issuer's DN: ...
recv>
recv>
recv> - Peer's certificate issuer is unknown
recv> - Peer's certificate is NOT trusted
recv> - Version: TLS1.0
recv> - Key Exchange: RSA
recv> - Cipher: ARCFOUR-128
recv> - MAC: MD5
recv> - Compression: NULL
send> ehlo rapitore.luna
recv> 250-...
recv> 250-SIZE 35651584
recv> 250-8BITMIME
recv> 250-AUTH LOGIN PLAIN
recv> 250-ENHANCEDSTATUSCODES
recv> 250 PIPELINING
send> auth plain <the-encoded-auth-credentials>
recv> 235 2.7.0 Accepted
send> mail from:<from-address@gmail.com>
recv> 250 2.1.0 OK ...
send> rcpt to:<to-address@poste.it>
recv> 250 2.1.5 OK ...
send> data
recv> 354 Go ahead ...
send> From: from-address@gmail.com
send> To: to-address@poste.it
send> Subject: interactive proof from gmail
send>
send> proof
send> .
recv> 250 2.0.0 OK ...
send> quit
recv> 221 2.0.0 closing connection ...
OpenSSL is a library implementing the SSL/TLS protocol; it can be used to establish encrypted and authenticated connections to a remote host.
An OpenSSL installation comes with a command line test program, openssl, that can be used to establish an encrypted connection by a shell script. This command can be used as the “connector” modeled in Send email through a process in background. It has a manual page, which we may want to read.
Here we see how we can use the openssl program in place of the gnutls-cli program described in Using gnutls-cli as connector. The two methods have a lot in common (the SMTP protocol is the same), we only have to understand the command line of the program.
Notice that openssl has a -crlf option that will cause all the lines sent to the server to be terminated by a carriage return/line feed sequence (\r\nor\x0d\x0a). If we write a script that terminates by itself the lines with this sequence, for example:printf 'ehlo localhost.localdomain\r\n'we must avoid this option, else SMTP protocol violation errors may occur. However, if we try a hand–driven interactive session, we want to use this option to send protocol–compliant lines.
To build the encrypted bridge right after the connection, without exchanging greetings with the server, we do:
$ openssl s_client -quiet -connect relay.poste.it:465
then we start the SMTP dialogue as outlined in Immediate encrypted bridge.
To first exchange greetings, then send STARTTLS and finally build
the encrypted bridge, we do:
$ openssl s_client -quiet -starttls smtp \
-connect smtp.gmail.com:587
then we start the SMTP dialogue as outlined in Immediate encrypted bridge. openssl knows how to start an SMTP dialogue, and it does it automatically.
This section documents the example script examples/sendmail-mbfl.sh, which makes use of MBFL to send an email message. The script sends already composed messages using plain or TLS sessions, and it can use both OpenSSL and GNU TLS.
Let's say we have a file named message.mail holding a fully composed email message:
Sender: marco@localhost
From: marco@localhost
To: root@localhost
Subject: server on fire?
I noticed flames raising from the server room...
--
Marco
basically, to send it with sendmail-mbfl.sh we have to do:
$ sendmail-mbfl.sh \
--envelope-from=marco@localhost \
--envelope-to=root@localhost \
--message=message.mail
by default the SMTP server name is set to localhost and the
TCP port to 25. The default session is plain, without TLS.
Port number 25 is officially assigned to the SMTP protocol; if
the localhost uses a different TCP port, we can select it with the
--port option:
$ sendmail-mbfl.sh --port=587 \
--envelope-from=marco@localhost \
--envelope-to=root@localhost \
--message=message.mail
To send mail to a remote SMTP server, we select its hostname with the --host option:
$ sendmail-mbfl.sh \
--host=smtp.gmail.com --port=587 \
--envelope-from=marco@gmail.com \
--envelope-to=marco@spiffy.it \
--message=message.mail
Mail services may offer encrypted sessions to their SMTP servers. Encryption with the TLS protocol is supported by sendmail-mbfl.sh through external programs. Whether a server requires an encrypted session, can be specified using the --plain, --tls or --starttls options.
To establish an encrypted session, the script needs to acquire the credentials of the user. These can be stored in a configuration file named ~/.authinfo, which looks like this:
machine smtp.gmail.com login marco@gmail.com password abcdefghilm
machine relay.poste.it login marco@poste.it password 0123456789
so that a line/record can be uniquely identified with values of the --host and --username options.
So we can do:
$ sendmail-mbfl.sh --host=gmail \
--username=marco --starttls \
--envelope-from=marco@gmail.com \
--envelope-to=marco@spiffy.it \
--message=message.mail
The synopsis is:
sendmail-mbfl.sh \
--envelope-from=<ADDRESS> \
--envelope-to=<ADDRESS> \
[--message=<SOURCE> | --test-message]
[options]
the script sends an email address, and it can do a plain session or use a connector. Options description follows.
-Faddress--envelope-from=addressMAIL FROM envelope address. If this option is used
multiple times: the last one wins.
-Taddress--envelope-to=addressRCPT TO envelope address. This option can be used
multiple times: each address is appended to a list of recipients.
-Msource--message=source-, the message is read from the standard input channel. It
defaults to -.
--test-message--host=hostlocalhost.
--host-info=file-pport--port=port25.
--plain--tls--starttlsSTARTTLS command.
--gnutls--openssl--auth-info=filenetrc format, from which read the
authorisation credentials. Defaults to ~/.authinfo.
--username=user--auth-none--auth-plain--auth-loginAUTH PLAIN authorisation or the
AUTH LOGIN authorisation.
For each SMTP server we need the following informations: the hostname, the port number, the session type, the authorisation method. There are two ways to specify these:
$ sendmail-mbfl.sh \
--host=smtp.gmail.org --port=587 \
--tls --auth-login \
...
$ sendmail-mbfl.sh --host=smtp.gmail.org ...
$ sendmail-mbfl.sh --host=smtp.gmail.org \
--host-info=~/.hostrc ...
If --host is not used: the hostname defaults to
localhost. If the selection of port, session type or
authorisation type is left unspecified: The script automatically looks
into the default hostinfo file. Informations from command line options
supersede informations from the hostinfo file.
The default pathname for the hostinfo file is $HOME/.hostinfo and
can be overridden by the --host-info option. The format of
this file is line oriented: Blank lines are ignored, lines starting with
a # character are comments, lines starting with machine
are host records.
Each record line must have the format:
machine <host> service <name> port <number> session <type> auth <type>
for example:
# ~/.hostinfo --
#
# SMTP servers
machine localhost service smtp port 25 session plain auth none
machine relay.poste.it service smtp port 465 session tls auth login
machine smtp.gmail.com service smtp port 587 session starttls auth plain
# POP3 servers
machine pop.tiscali.it service pop3 port 110 session plain auth userpass
machine relay.poste.it service pop3 port 995 session tls auth userpass
machine pop.googlemail.com service pop3 port 995 session tls auth userpass
### end of file
so that we can extract a record with the following script:
file=~/.hostinfo
host=gmail
service=smtp
rex='^[ \t]*'
rex+='machine[ \t]\+.*%s.*[ \t]\+'
rex+='service[ \t]\+%s[ \t]\+'
rex+='port[ \t]\+[0-9]\+[ \t]\+'
rex+='session[ \t]\+\(plain\|tls\|starttls\)[ \t]\+'
rex+='auth[ \t]\+\(none\|plain\|login\)'
rex+='[ \t]*$'
rex=$(printf "$rex" $host $service)
set -- $(grep "$rex" "$file")
echo machine $2
echo service $4
echo port $6
echo session $8
shift 9
echo auth $1
this is exactly what sendmail-mbfl.sh does with the file.
Notice that when using the hostinfo file, the value of the --host option can be a substring of the host name.
The only way the script has to acquire the user name and password to log into the remote server, is by reading the authinfo file. By default, its pathname is $HOME/.authinfo, it can be overridden with the --auth-info option.
Its format is a simplified version of the netrc file format:
blank lines are ignored, lines starting with a # character are
comments, lines starting with machine are host records.
Each record must have the format:
machine <hostname> login <user-name> password <pass-word>
for example:
machine smtp.gmail.com login one@gmail.com password abcdefghilm
machine relay.poste.it login two@poste.it password 0123456789
so that we can extract a record with the following script:
file=~/.authinfo
host=poste
username=marco
rex='^[ \t]*'
rex+='machine[ \t]\+.*%s.*[ \t]\+'
rex+='login[ \t]\+.*%s.*[ \t]\+'
rex+='password[ \t]\+.*'
rex+='[ \t]*$'
rex=$(printf "$rex" $host $username)
line=$(grep "$rex" $file)
set -- $line
echo machine $2
echo username $4
echo password $6
and this is exactly what sendmail-mbfl.sh does.
The host name and the user name are selected by the command line options --host and --username. Notice that the values for these options can be substrings of the values in the authinfo file.
This section documents an Emacs interface to the sendmail-mbfl.sh script that can be used to send mail. It consists of an Emacs Lisp library that allows us to send mail with all the methods supported by the script.
To use it we have to install the file examples/sendmail-mbfl.el in one of the directories in the load path of Emacs, then load it with:
(require 'sendmail-mbfl)
The library assumes that message.el and sendmail.el are available on the system; recent installations of Emacs should have them.
A customisation group called sendmail-mbfl is available to
configure the library.
A string representing the name of the MBFL shell script. By default set to
sendmail-mbfl.sh.
A list of strings representing extra arguments for the command line of sendmail-mbfl.sh. By default set to:
("--verbose" "--debug")
Select a function to call to acquire, from the current buffer, the envelope email address of the sender, to be used in the
MAIL FROMSMTP command.The function is invoked with no arguments and it must return a single string representing the email address. If no suitable address is found: it must raise an error. The function may be called multiple times for the same message buffer.
The selected function is used by
send-mail-with-mbfl. By default it is set tosendmail-mbfl-envelope-from.
Select a function to call to acquire, from the current buffer, the envelope email addresses of the receivers, to be used in the
RCPT TOSMTP command.The function is invoked with no arguments and it must return a list of strings representing email addresses. If no suitable address is found: it must raise an error. The function may be called multiple times for the same message buffer.
The selected function is used by
send-mail-with-mbfl. By default it is set tosendmail-mbfl-envelope-to.
Select a function to call to extract a list of email addresses from an email header. It is invoked with no arguments and the buffer narrowed to the header to examine.
The function is invoked with no arguments and it must return a list of strings representing email addresses, or nil.
The selected function is used by
sendmail-mbfl-envelope-fromandsendmail-mbfl-envelope-to.
Select a function to call to extract, from the current buffer, the hostname of the SMTP server to be used to send the message. The result is used as search key in the selected hostinfo file.
The function is invoked with no arguments and it must return a string representing the hostname; if it is unable to determine the hostname: it must raise an error.
The selected function is used by
send-mail-with-mbfl. By default it is set tosendmail-mbfl-hostname.
Select a function to call to extract, from the current buffer, the username with which to login to the SMTP server. The result is used as search key in the selected authinfo file.
The function is invoked with no arguments and it must return a string representing the username; if it is unable to determine the username: it must raise an error.
The selected function is used by
send-mail-with-mbfl. By default it is set tosendmail-mbfl-username.
The pathname of the file holding informations about known SMTP servers. Reading host informations from file.
By default it is set to ~/.hostinfo.
The pathname of the file holding informations about known accounts at SMTP servers. Reading authentication credentials from file.
By default it is set to ~/.authinfo.
Select the external program to use to establish the TLS transport layer. Valid values are the strings:
gnutls,openssl. The default isopensslbecause it is more likely to be installed on any system.
Select the timeout in seconds for reading answers from the SMTP server. The default is 5.
Send the email message in the current buffer. This interactive function can be invoked directly by the user, or, better, used as value for
message-send-mail-function. Sending involves:
- Normalising the message with
sendmail-mbfl-normalise-message.- Performing special deliveries with
sendmail-mbfl-delivery.- Posting the message to an MTA using
sendmail-mbfl-post.
Perform special deliveries of the email message in the current buffer. If the message has an
Fccheader, deliver is performed relying onmail-do-fccfrom sendmail.el.It is to be called after
sendmail-mbfl-normalise-messageor an equivalent normalisation has been applied to the message.
Post the email message in the current buffer using an MTA. Posting involves:
- Preparing the message with
sendmail-mbfl-prepare-message-for-mta.- Saving the message into a temporary file.
- Sending the file using the program selected with the customisable variable
sendmail-mbfl-program.It is to be called after
sendmail-mbfl-normalise-messageor an equivalent normalisation has been applied to the message.
sendmail-mbfl-envelope-frominterprets the current buffer as an email message and searches the contents for an email address to be used as envelope sender.It examines the headers
FromandSender, in this order and it returns a single string representing the email address; if no suitable address is found: it raises an error. The address is extracted from the headers using the function selected by the customisable variablesendmail-mbfl-extract-addresses-function.
sendmail-mbfl-envelope-from/messageis an interactive wrapper forsendmail-mbfl-envelope-fromthat prints the result to the*Message*buffer.
sendmail-mbfl-envelope-tointerprets the current buffer as an email message and searches the contents for email addresses to be used as envelope receivers.It examines the headers
To,CcandBccand it returns a list of strings representing email addresses; if no suitable address is found: it raises an error. The addresses are extracted from the headers using the function selected by the customisable variablesendmail-mbfl-extract-addresses-function.
sendmail-mbfl-envelope-to/messageis an interactive wrapper forsendmail-mbfl-envelope-tothat prints the result to the*Message*buffer.
Extract a list of email addresses from the current buffer. It must be invoked with the buffer narrowed to the header to examine. Return a list of email addresses as strings, or nil if no address is found.
sendmail-mbfl-hostnameextracts, from the current buffer, the hostname of the SMTP server to be used to send the message. The result is used as search key in the selected hostinfo file.It returns a string representing the hostname else, if unable to determine the hostname, it raises an error. The hostname is the hostname part of the value returned by
sendmail-mbfl-envelope-from.
sendmail-mbfl-hostname/messageis an interactive wrapper forsendmail-mbfl-hostnamethat prints the result to the*Message*buffer.
sendmail-mbfl-usernameextracts, from the current buffer, the username with which login to the SMTP server. The result is used as search key in the selected authinfo file.It returns a string representing the username else, if unable to determine the username, it raises an error. The username is the username part of the value returned by
sendmail-mbfl-envelope-from.
sendmail-mbfl-username/messageis an interactive wrapper forsendmail-mbfl-usernamethat prints the result to the*Message*buffer.
Set
message-send-mail-functionso that functions from message.el send mail usingsend-mail-with-mbfl.
Normalise the email message in the current buffer so that it is ready to be posted or delivered. Scan the headers for invalid lines and try to fix them. Scan the message for mandatory headers and, if missing, add them; this may require querying the user for informations.
It is to be called before acquiring sender and receiver addresses from the headers. It is an interactive function: it can be explicitly applied to a buffer by the user any number of times.
This function does not remove the headers/body separator.
Prepare the email message in the current buffer to be sent to a Mail Transport Agent. Headers like
FccandBccare removed; the headers/body separator line is removed. It is to be called after acquiring sender and receiver addresses from the headers.
Copy an email message from src-buffer to dst-buffer. Set encoding and text representation properties of the destination buffer to be equal to the ones of the source buffer.
Copyright © 2007 Free Software Foundation, Inc. http://fsf.org/
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below.
As used herein, “this License” refers to version 3 of the GNU Lesser General Public License, and the “GNU GPL” refers to version 3 of the GNU General Public License.
“The Library” refers to a covered work governed by this License, other than an Application or a Combined Work as defined below.
An “Application” is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library.
A “Combined Work” is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the “Linked Version”.
The “Minimal Corresponding Source” for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version.
The “Corresponding Application Code” for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work.
You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL.
If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version:
The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following:
You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following:
You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following:
The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library.
Copyright © 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc.
http://fsf.org/
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
The purpose of this License is to make a manual, textbook, or other functional and useful document free in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others.
This License is a kind of “copyleft”, which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software.
We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference.
This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work under the conditions stated herein. The “Document”, below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as “you”. You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law.
A “Modified Version” of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language.
A “Secondary Section” is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document's overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them.
The “Invariant Sections” are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. If a section does not fit the above definition of Secondary then it is not allowed to be designated as Invariant. The Document may contain zero Invariant Sections. If the Document does not identify any Invariant Sections then there are none.
The “Cover Texts” are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words.
A “Transparent” copy of the Document means a machine-readable copy, represented in a format whose specification is available to the general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modification by readers is not Transparent. An image format is not Transparent if used for any substantial amount of text. A copy that is not “Transparent” is called “Opaque”.
Examples of suitable formats for Transparent copies include plain ascii without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standard-conforming simple HTML, PostScript or PDF designed for human modification. Examples of transparent image formats include PNG, XCF and JPG. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machine-generated HTML, PostScript or PDF produced by some word processors for output purposes only.
The “Title Page” means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, “Title Page” means the text near the most prominent appearance of the work's title, preceding the beginning of the body of the text.
The “publisher” means any person or entity that distributes copies of the Document to the public.
A section “Entitled XYZ” means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. (Here XYZ stands for a specific section name mentioned below, such as “Acknowledgements”, “Dedications”, “Endorsements”, or “History”.) To “Preserve the Title” of such a section when you modify the Document means that it remains a section “Entitled XYZ” according to this definition.
The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no effect on the meaning of this License.
You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3.
You may also lend copies, under the same conditions stated above, and you may publicly display copies.
If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and the Document's license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects.
If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages.
If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public.
It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document.
You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version:
If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modified Version's license notice. These titles must be distinct from any other section titles.
You may add a section Entitled “Endorsements”, provided it contains nothing but endorsements of your Modified Version by various parties—for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard.
You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one.
The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version.
You may combine the Document with other documents released under this License, under the terms defined in section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers.
The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work.
In the combination, you must combine any sections Entitled “History” in the various original documents, forming one section Entitled “History”; likewise combine any sections Entitled “Acknowledgements”, and any sections Entitled “Dedications”. You must delete all sections Entitled “Endorsements.”
You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects.
You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document.
A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, is called an “aggregate” if the copyright resulting from the compilation is not used to limit the legal rights of the compilation's users beyond what the individual works permit. When the Document is included in an aggregate, this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document.
If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the entire aggregate, the Document's Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole aggregate.
Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the translation and the original version of this License or a notice or disclaimer, the original version will prevail.
If a section in the Document is Entitled “Acknowledgements”, “Dedications”, or “History”, the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title.
You may not copy, modify, sublicense, or distribute the Document except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, or distribute it is void, and will automatically terminate your rights under this License.
However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.
Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, receipt of a copy of some or all of the same material does not give you any rights to use it.
The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See http://www.gnu.org/copyleft/.
Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License “or any later version” applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation. If the Document specifies that a proxy can decide which future versions of this License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Document.
“Massive Multiauthor Collaboration Site” (or “MMC Site”) means any World Wide Web server that publishes copyrightable works and also provides prominent facilities for anybody to edit those works. A public wiki that anybody can edit is an example of such a server. A “Massive Multiauthor Collaboration” (or “MMC”) contained in the site means any set of copyrightable works thus published on the MMC site.
“CC-BY-SA” means the Creative Commons Attribution-Share Alike 3.0 license published by Creative Commons Corporation, a not-for-profit corporation with a principal place of business in San Francisco, California, as well as future copyleft versions of that license published by that same organization.
“Incorporate” means to publish or republish a Document, in whole or in part, as part of another Document.
An MMC is “eligible for relicensing” if it is licensed under this License, and if all works that were first published under this License somewhere other than this MMC, and subsequently incorporated in whole or in part into the MMC, (1) had no cover texts or invariant sections, and (2) were thus incorporated prior to November 1, 2008.
The operator of an MMC Site may republish an MMC contained in the site under CC-BY-SA on the same site at any time before August 1, 2009, provided the MMC is eligible for relicensing.
To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page:
Copyright (C) year your name.
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.3
or any later version published by the Free Software Foundation;
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover
Texts. A copy of the license is included in the section entitled ``GNU
Free Documentation License''.
If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace the “with...Texts.” line with this:
with the Invariant Sections being list their titles, with
the Front-Cover Texts being list, and with the Back-Cover Texts
being list.
If you have Invariant Sections without Cover Texts, or some other combination of the three, merge those two alternatives to suit the situation.
If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software.
See bash.
Lots of interesting stuff at the following site:
command_line_argument: preprocessor macrosconnector: sendmail connectordotest: testing runningdotest-assert-file-exists: testing files filesdotest-assert-file-unexists: testing files filesdotest-cd: testing files directoriesdotest-cd-tmpdir: testing files directoriesdotest-clean-files: testing files filesdotest-debug: testing messagesdotest-echo: testing messagesdotest-echo-tmpdir: testing files directoriesdotest-equal: testing comparedotest-mkdir: testing files directoriesdotest-mkfile: testing files filesdotest-mktmpdir: testing files directoriesdotest-option-report-start: testing configdotest-option-report-success: testing configdotest-option-test: testing configdotest-option-verbose: testing configdotest-output: testing outputdotest-set-report-start: testing configdotest-set-report-success: testing configdotest-set-test: testing configdotest-set-verbose: testing configdotest-unset-report-start: testing configdotest-unset-report-success: testing configdotest-unset-test: testing configdotest-unset-verbose: testing configexit_because_failure: main exitexit_because_program_not_found: program declaringexit_because_success: main exitexit_because_wrong_num_args: getopts interfaceexit_failure: main exitexit_success: main exitFunction: testing outputmain: sendmail plainmandatory_parameter: preprocessor macrosmbfl_argv_all_files: getopts interfacembfl_argv_from_stdin: getopts interfacembfl_at_drop: interfaces atmbfl_at_enable: interfaces atmbfl_at_print_queue: interfaces atmbfl_at_queue_clean: interfaces atmbfl_at_queue_print_identifiers: interfaces atmbfl_at_queue_print_jobs: interfaces atmbfl_at_queue_print_queues: interfaces atmbfl_at_schedule: interfaces atmbfl_at_select_queue: interfaces atmbfl_at_validate_queue_letter: interfaces atmbfl_at_validate_selected_queue: interfaces atmbfl_cd: file miscmbfl_change_directory: file miscmbfl_declare_action_argument: getopts interfacembfl_declare_option: getopts interfacembfl_declare_program: program declaringmbfl_decode_hex: encodingmbfl_decode_oct: encodingmbfl_dialog_ask_password: dialogmbfl_dialog_enable_programs: dialogmbfl_dialog_yes_or_no: dialogmbfl_file_append: file read and writembfl_file_compress: file commands compressmbfl_file_compress_keep: file commands compressmbfl_file_compress_nokeep: file commands compressmbfl_file_compress_select_bzip: file commands compressmbfl_file_compress_select_gzip: file commands compressmbfl_file_compress_select_nostdout: file commands compressmbfl_file_compress_select_stdout: file commands compressmbfl_file_copy: file commands copymbfl_file_copy_to_directory: file commands copymbfl_file_decompress: file commands compressmbfl_file_directory_is_executable: file testingmbfl_file_directory_is_readable: file testingmbfl_file_directory_is_writable: file testingmbfl_file_directory_validate_writability: file testingmbfl_file_dirname: file name partsmbfl_file_enable_compress: file commands compressmbfl_file_enable_copy: file commands copymbfl_file_enable_listing: file commands listingmbfl_file_enable_make_directory: file commands mkdirmbfl_file_enable_move: file commands movembfl_file_enable_permissions: file commands permsmbfl_file_enable_remove: file commands removingmbfl_file_enable_symlink: file commands symlinkmbfl_file_enable_tar: file commands tarmbfl_file_exists: file testingmbfl_file_extension: file name partsmbfl_file_find_tmpdir: file name systemmbfl_file_get_group: file commands listingmbfl_file_get_owner: file commands listingmbfl_file_get_permissions: file commands permsmbfl_file_get_size: file commands listingmbfl_file_is_absolute: file name pathmbfl_file_is_absolute_dirname: file name pathmbfl_file_is_absolute_filename: file name pathmbfl_file_is_directory: file testingmbfl_file_is_executable: file testingmbfl_file_is_file: file testingmbfl_file_is_readable: file testingmbfl_file_is_symlink: file testingmbfl_file_is_writable: file testingmbfl_file_listing: file commands listingmbfl_file_long_listing: file commands listingmbfl_file_make_directory: file commands mkdirmbfl_file_make_if_not_directory: file commands mkdirmbfl_file_move: file commands movembfl_file_move_to_directory: file commands movembfl_file_normalise: file name pathmbfl_file_normalise_link: file commands listingmbfl_file_pathname_is_executable: file testingmbfl_file_pathname_is_readable: file testingmbfl_file_pathname_is_writable: file testingmbfl_file_read: file read and writembfl_file_read_link: file commands listingmbfl_file_remove: file commands removingmbfl_file_remove_directory: file commands removingmbfl_file_remove_directory_silently: file commands removingmbfl_file_remove_file: file commands removingmbfl_file_remove_file_or_symlink: file commands removingmbfl_file_remove_symlink: file commands removingmbfl_file_rootname: file name partsmbfl_file_set_permissions: file commands permsmbfl_file_split: file name partsmbfl_file_strip_trailing_slash: file name partsmbfl_file_subpathname: file name pathmbfl_file_symlink: file commands symlinkmbfl_file_tail: file name partsmbfl_file_write: file read and writembfl_getopts_is_action_argument: getopts interfacembfl_getopts_isbrief: getopts interfacembfl_getopts_isbrief_with: getopts interfacembfl_getopts_islong: getopts interfacembfl_getopts_islong_with: getopts interfacembfl_getopts_parse: getopts interfacembfl_getopts_print_action_arguments: getopts interfacembfl_getopts_print_long_switches: getopts interfacembfl_invoke_script_function: main functionmbfl_main: main functionmbfl_main_create_exit_functions: main exitmbfl_main_declare_exit_code: main exitmbfl_main_print_exit_code: main exitmbfl_main_set_main: main functionmbfl_message_debug: messagembfl_message_debug_printf: messagembfl_message_error: messagembfl_message_error_printf: messagembfl_message_set_channel: messagembfl_message_set_progname: messagembfl_message_string: messagembfl_message_verbose: messagembfl_message_verbose_end: messagembfl_message_verbose_printf: messagembfl_message_warning: messagembfl_message_warning_printf: messagembfl_option_debug: getopts optionsmbfl_option_encoded_args: getopts optionsmbfl_option_interactive: getopts optionsmbfl_option_null: getopts optionsmbfl_option_show_program: getopts optionsmbfl_option_test: program testingmbfl_option_test: getopts optionsmbfl_option_test_restore: getopts optionsmbfl_option_test_save: getopts optionsmbfl_option_verbose: getopts optionsmbfl_option_verbose_program: getopts optionsmbfl_program_bash: program executingmbfl_program_bash_command: program executingmbfl_program_check: program checkingmbfl_program_declare_sudo_user: program executingmbfl_program_enable_sudo: program executingmbfl_program_exec: program executingmbfl_program_execbg: program executingmbfl_program_find: program checkingmbfl_program_found: program declaringmbfl_program_redirect_stderr_to_stdout: program executingmbfl_program_requested_sudo: program executingmbfl_program_reset_sudo_user: program executingmbfl_program_sudo_user: program executingmbfl_program_validate_declared: program declaringmbfl_read_maybe_null: basembfl_set_maybe: basembfl_set_option_debug: getopts optionsmbfl_set_option_encoded_args: getopts optionsmbfl_set_option_interactive: getopts optionsmbfl_set_option_null: getopts optionsmbfl_set_option_show_program: getopts optionsmbfl_set_option_test: program testingmbfl_set_option_test: getopts optionsmbfl_set_option_verbose: getopts optionsmbfl_set_option_verbose_program: getopts optionsmbfl_signal_attach: signalmbfl_signal_invoke_handlers: signalmbfl_signal_map_signame_to_signum: signalmbfl_sprintf: string miscmbfl_string_chars: string splittingmbfl_string_equal_substring: string inspectionmbfl_string_first: string inspectionmbfl_string_index: string inspectionmbfl_string_is_alnum: string classmbfl_string_is_alnum_char: string classmbfl_string_is_alpha: string classmbfl_string_is_alpha_char: string classmbfl_string_is_digit: string classmbfl_string_is_digit_char: string classmbfl_string_is_equal_unquoted_char: string quotembfl_string_is_name: string classmbfl_string_is_name_char: string classmbfl_string_is_noblank: string classmbfl_string_is_noblank_char: string classmbfl_string_is_quoted_char: string quotembfl_string_last: string inspectionmbfl_string_quote: string quotembfl_string_range: string inspectionmbfl_string_replace: string miscmbfl_string_skip: string miscmbfl_string_split: string splittingmbfl_string_tolower: string casembfl_string_toupper: string casembfl_system_enable_programs: systemmbfl_system_numerical_user_id_to_name: system user idmbfl_system_octal_to_symbolic_permissions: system file permsmbfl_system_symbolic_to_octal_permissions: system file permsmbfl_tar_archive_directory_to_file: file commands tarmbfl_tar_create_to_file: file commands tarmbfl_tar_create_to_stdout: file commands tarmbfl_tar_exec: file commands tarmbfl_tar_extract_from_file: file commands tarmbfl_tar_extract_from_stdin: file commands tarmbfl_tar_list: file commands tarmbfl_unset_option_debug: getopts optionsmbfl_unset_option_encoded_args: getopts optionsmbfl_unset_option_interactive: getopts optionsmbfl_unset_option_null: getopts optionsmbfl_unset_option_show_program: getopts optionsmbfl_unset_option_test: program testingmbfl_unset_option_test: getopts optionsmbfl_unset_option_verbose: getopts optionsmbfl_unset_option_verbose_program: getopts optionsmbfl_variable_array_to_colon_variable: variables colonmbfl_variable_colon_variable_drop_duplicate: variables colonmbfl_variable_colon_variable_to_array: variables colonmbfl_variable_element_is_in_array: variables arraysmbfl_variable_find_in_array: variables arraysmbfl_wrong_num_args: getopts interfacembfl_wrong_num_args_range: getopts interfaceopen_session: sendmail connectoropen_session: sendmail plainoptional_parameter: preprocessor macrosread_and_send_message: sendmail plainrecv: sendmail plainsend: sendmail plainsend-mail-with-mbfl: sendmail script emacs sendsendmail-mbfl-activate: sendmail script emacs miscsendmail-mbfl-copy-message-buffer: sendmail script emacs miscsendmail-mbfl-delivery: sendmail script emacs sendsendmail-mbfl-envelope-from: sendmail script emacs inspsendmail-mbfl-envelope-from/message: sendmail script emacs inspsendmail-mbfl-envelope-to: sendmail script emacs inspsendmail-mbfl-envelope-to/message: sendmail script emacs inspsendmail-mbfl-extract-addresses: sendmail script emacs inspsendmail-mbfl-hostname: sendmail script emacs inspsendmail-mbfl-hostname/message: sendmail script emacs inspsendmail-mbfl-normalise-message: sendmail script emacs miscsendmail-mbfl-post: sendmail script emacs sendsendmail-mbfl-prepare-message-for-mta: sendmail script emacs miscsendmail-mbfl-username: sendmail script emacs inspsendmail-mbfl-username/message: sendmail script emacs inspmbfl_main_SCRIPT_FUNCTION: main functionmbfl_program_BGPID: program executingscript_AUTHOR: service variablesscript_COPYRIGHT_YEARS: service variablesscript_DESCRIPTION: service variablesscript_EXAMPLES: service variablesscript_LICENSE: service variablesscript_PROGNAME: service variablesscript_USAGE: service variablesscript_VERSION: service variablessendmail-mbfl-auth-info: sendmail script emacs varssendmail-mbfl-connector: sendmail script emacs varssendmail-mbfl-envelope-from-function: sendmail script emacs varssendmail-mbfl-envelope-to-function: sendmail script emacs varssendmail-mbfl-extra-args: sendmail script emacs varssendmail-mbfl-extract-addresses-function: sendmail script emacs varssendmail-mbfl-host-info: sendmail script emacs varssendmail-mbfl-hostname-function: sendmail script emacs varssendmail-mbfl-program: sendmail script emacs varssendmail-mbfl-timeout: sendmail script emacs varssendmail-mbfl-username-function: sendmail script emacs varsTESTMATCH: testing runningTESTSTART: testing runningTESTSUCCESS: testing running[1] TCL stands for Tool Command Language and it is a scripting language originally written by John Ousterhout, see: http://www.tcl.tk/.