BASIS  r3148
core.sh
Go to the documentation of this file.
00001 ##############################################################################
00002 # @file  core.sh
00003 # @brief Core functions for Bash development.
00004 #
00005 # This is the core module of the BASIS utilities for Bash. It implements
00006 # fundamental functions for the development in Bash. Therefore, this module
00007 # has to be kept independent of any other modules and shall only make use
00008 # of Bash builtin's and basic commands.
00009 #
00010 # Copyright (c) 2011, 2012 University of Pennsylvania. All rights reserved.<br />
00011 # See https://www.cbica.upenn.edu/sbia/software/license.html or COPYING file.
00012 #
00013 # Contact: SBIA Group <sbia-software at uphs.upenn.edu>
00014 ##############################################################################
00015 
00016 [ "${_BASIS_CORE_INCLUDED}" == 'true' ] || {
00017 _BASIS_CORE_INCLUDED='true'
00018 
00019 
00020 . "`cd -P -- \`dirname -- "${BASH_SOURCE}"\` && pwd`/config.sh" || exit 1
00021 
00022 
00023 ## @addtogroup BasisBashUtilities
00024 #  @{
00025 
00026 
00027 # ============================================================================
00028 # module import
00029 # ============================================================================
00030 
00031 # ----------------------------------------------------------------------------
00032 ## @brief Import Bash module.
00033 #
00034 # This function can be used to import, i.e., source, a named module, where the
00035 # module is searched for in the directories specified by the @c BASHPATH
00036 # (environment) variable. Modules can be prepended by the names of the
00037 # subdirectories they are located in relative to a directory listed in the
00038 # @c BASHPATH, where dots (.) can be used instead of slashes to separate the
00039 # parts of the relative module path. Moreover, the <tt>.sh</tt> extension of
00040 # the modules is appended to the module name if not specified.
00041 #
00042 # Example:
00043 # @code
00044 # import sbia.basis.core
00045 # import -o optional.module
00046 # if [ $? -eq 0 ]; then
00047 #     echo "Module optional.module imported successfully"
00048 # else
00049 #     echo "Module optional.module not found"
00050 # fi
00051 # @endcode
00052 #
00053 # @note The directories listed in the @c BASHPATH must be absolute paths
00054 #       starting with a forward slash (/).
00055 #
00056 # @param [in] options Option flags. The only possible option is -o. If this
00057 #                     option is given before the module name, the function
00058 #                     returns with return value 1 on error. Otherwise, it
00059 #                     calls the exit function with this exit code.
00060 #                     It can therefore be used for optional modules, which
00061 #                     do not necessarily need to be imported.
00062 # @param [in] module  Name of the module to import.
00063 #
00064 # @returns Only on success or if the -o option is given. Otherwise it calls
00065 #          the exit function if an error occurs.
00066 #
00067 # @retval 0 if the module was loaded successfully.
00068 # @retval 1 on error if -o option is given.
00069 import()
00070 {
00071     if [[ -z "${BASHPATH}" ]]; then
00072         echo "import: BASHPATH not specified!" 1>&2
00073         exit 1
00074     fi
00075     local exitonerror='true'
00076     while [ $# -gt 0 ]; do
00077         case "$1" in
00078             -o) exitonerror='false'; ;;
00079             *)  break; ;;
00080         esac
00081         shift
00082     done
00083     local module="${1//.//}" # replace dots (.) by forward slashes (/)
00084     if [[ -z "${module}" ]]; then
00085         echo "import: missing module name argument!" 1>&2
00086         exit 1
00087     fi
00088     if [[ $# -gt 1 ]]; then
00089         echo "import: too many arguments!" 1>&2
00090         exit 1
00091     fi
00092     [[ ${module: -3} == '.sh' ]] || module="${module}.sh"
00093     local path="${BASHPATH}:" # ATTENTION: Trailing ':' required to terminate while loop!
00094     local root=
00095     while [[ -n "${path}" ]]; do
00096         root="${path%%:*}"
00097         if [[ "${root:0:1}" == '/' && -f "${root}/${module}" ]]; then
00098             . "${root}/${module}"
00099             return 0
00100         fi
00101         path="${path#*:}"
00102     done
00103     if [[ ${exitonerror} == 'true' ]]; then
00104         echo "import: module '$1' not found!" 1>&2
00105         exit 1
00106     fi
00107     return 1
00108 }
00109 
00110 # ============================================================================
00111 # pattern matching
00112 # ============================================================================
00113 
00114 # ----------------------------------------------------------------------------
00115 ## @brief This function implements a more portable way to do pattern matching.
00116 #
00117 # Unfortunately, there are significant differences in the way patterns have
00118 # to be matched when using different shells. This function considers which
00119 # shell is used (at the moment only BASH), and uses the appropriate syntax
00120 # for the pattern matching.
00121 #
00122 # @param [in] value   The string to match against pattern.
00123 # @param [in] pattern The pattern to match.
00124 #
00125 # @returns Whether the given string matches the given pattern.
00126 #
00127 # @retval 0 On match.
00128 # @retval 1 Otherwise.
00129 match()
00130 {
00131     [ $# -eq 2 ] || return 1
00132 
00133     local value=$1
00134     local pattern=$2
00135 
00136     if [ -z "${value}" ]; then
00137         [ -z "${pattern}" ]
00138     elif [ -z "${pattern}" ]; then
00139         [ -z "${value}" ]
00140     else
00141         if [ ${BASH_VERSION_MAJOR} -gt 2 ]; then
00142             # GNU bash, version 3.00.15(1)-release (x86_64-redhat-linux-gnu)
00143             # throws an error when a regular expression with groups
00144             # such as in '^(a|b|c)' is used. Here, quotes are required.
00145             if [ ${BASH_VERSION_MAJOR} -eq 3 -a ${BASH_VERSION_MINOR} -eq 0 ]; then
00146                 [[ "${value}" =~ "${pattern}" ]]
00147             # GNU bash, version 3.2.25(1)-release (x86_64-redhat-linux-gnu)
00148             # works with either quotes or not. However, on Mac OS Snow Leopard,
00149             # GNU bash, version 3.2.48(1)-release (x86_64-apple-darwin10.0)
00150             # requires that no quotes are used. The quotes are otherwise
00151             # considered to be part of the pattern.
00152             else
00153                 [[ "${value}" =~ ${pattern} ]]
00154             fi
00155         else
00156             echo "${value}" | egrep -q "${pattern}"
00157         fi
00158     fi
00159 }
00160 
00161 # ============================================================================
00162 # upvar(s)
00163 # ============================================================================
00164 
00165 # ----------------------------------------------------------------------------
00166 ## @brief Assign variable one scope above the caller.
00167 #
00168 # This function can be used inside functions to return values by assigning
00169 # them to a variable in the scope of the caller.
00170 #
00171 # @note For assigning multiple variables, use upvars(). Do NOT use multiple
00172 #       upvar() calls, since one upvar() call might reassign a variable to
00173 #       be used by another upvar() call.
00174 #
00175 # Example:
00176 # @code
00177 # foo ()
00178 # {
00179 #     local "$1" && upvar $1 "Hello, World!"
00180 # }
00181 #
00182 # foo greeting
00183 # echo ${greeting}
00184 # @endcode
00185 #
00186 # @param [in] var    Variable name to assign value to
00187 # @param [in] values Value(s) to assign. If multiple values, an array is
00188 #                    assigned, otherwise a single value is assigned.
00189 #
00190 # @returns Nothing.
00191 #
00192 # @retval 0 On success.
00193 # @retval 1 On failure.
00194 #
00195 # @sa upvars()
00196 # @sa http://fvue.nl/wiki/Bash:_Passing_variables_by_reference
00197 upvar()
00198 {
00199     if unset -v "$1"; then           # Unset & validate varname
00200         if (( $# == 2 )); then
00201             eval $1=\"\$2\"          # Return single value
00202         else
00203             eval $1=\(\"\${@:2}\"\)  # Return array
00204         fi
00205     fi
00206 }
00207 
00208 # ----------------------------------------------------------------------------
00209 ## @brief Assign variables one scope above the caller.
00210 #
00211 # @par Synopsis
00212 # local varname [varname ...] && 
00213 # upvars [-v varname value] | [-aN varname [value ...]] ...
00214 #
00215 # @par Options:
00216 # - -aN  Assign next N values to varname as array
00217 # - -v   Assign single value to varname#
00218 #
00219 # @retval 0 On success.
00220 # @retval 1 On failure.
00221 #
00222 # @sa http://fvue.nl/wiki/Bash:_Passing_variables_by_reference
00223 upvars()
00224 {
00225     if ! (( $# )); then
00226         echo "${FUNCNAME[0]}: usage: ${FUNCNAME[0]} [-v varname"\
00227             "value] | [-aN varname [value ...]] ..." 1>&2
00228         return 2
00229     fi
00230     while (( $# )); do
00231         case $1 in
00232             -a*)
00233                 # Error checking
00234                 [[ ${1#-a} ]] || { echo "bash: ${FUNCNAME[0]}: \`$1': missing"\
00235                     "number specifier" 1>&2; return 1; }
00236                 printf %d "${1#-a}" &> /dev/null || { echo "bash:"\
00237                     "${FUNCNAME[0]}: \`$1': invalid number specifier" 1>&2
00238                     return 1; }
00239                 # Assign array of -aN elements
00240                 [[ "$2" ]] && unset -v "$2" && eval $2=\(\"\${@:3:${1#-a}}\"\) && 
00241                 shift $((${1#-a} + 2)) || { echo "bash: ${FUNCNAME[0]}:"\
00242                     "\`$1${2+ }$2': missing argument(s)" 1>&2; return 1; }
00243                 ;;
00244             -v)
00245                 # Assign single value
00246                 [[ "$2" ]] && unset -v "$2" && eval $2=\"\$3\" &&
00247                 shift 3 || { echo "bash: ${FUNCNAME[0]}: $1: missing"\
00248                 "argument(s)" 1>&2; return 1; }
00249                 ;;
00250             --help) echo "\
00251 Usage: local varname [varname ...] &&
00252    ${FUNCNAME[0]} [-v varname value] | [-aN varname [value ...]] ...
00253 Available OPTIONS:
00254 -aN VARNAME [value ...]   assign next N values to varname as array
00255 -v VARNAME value          assign single value to varname
00256 --help                    display this help and exit
00257 --version                 output version information and exit"
00258                 return 0 ;;
00259             --version) echo "\
00260 ${FUNCNAME[0]}-0.9.dev
00261 Copyright (C) 2010 Freddy Vulto
00262 License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
00263 This is free software: you are free to change and redistribute it.
00264 There is NO WARRANTY, to the extent permitted by law."
00265                 return 0 ;;
00266             *)
00267                 echo "bash: ${FUNCNAME[0]}: $1: invalid option" 1>&2
00268                 return 1 ;;
00269         esac
00270     done
00271 }
00272 
00273 
00274 ## @}
00275 # end of Doxygen group
00276 
00277 
00278 } # _BASIS_CORE_INCLUDED