BASIS  version 1.2.3 (revision 2104)
core.sh
Go to the documentation of this file.
00001 ##############################################################################
00002 # @file  core.sh
00003 # @brief Core functions for BASH.
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 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 # return if already loaded
00017 [ "${_SBIA_CORE_INCLUDED:-0}" -eq 1 ] && return 0
00018 _SBIA_CORE_INCLUDED=1
00019 
00020 
00021 ## @addtogroup BasisBashUtilities
00022 #  @{
00023 
00024 
00025 # ============================================================================
00026 # constants
00027 # ============================================================================
00028 
00029 BASH_VERSION_MAJOR=${BASH_VERSION%%.*}
00030 BASH_VERSION_MINOR=${BASH_VERSION#*.}
00031 BASH_VERSION_MINOR=${BASH_VERSION_MINOR%%.*}
00032 
00033 readonly BASH_VERSION_MAJOR
00034 readonly BASH_VERSION_MINOR
00035 
00036 # ============================================================================
00037 # pattern matching
00038 # ============================================================================
00039 
00040 # ----------------------------------------------------------------------------
00041 ## @brief This function implements a more portable way to do pattern matching.
00042 #
00043 # Unfortunately, there are significant differences in the way patterns have
00044 # to be matched when using different shells. This function considers which
00045 # shell is used (at the moment only BASH), and uses the appropriate syntax
00046 # for the pattern matching.
00047 #
00048 # @param [in] value   The string to match against pattern.
00049 # @param [in] pattern The pattern to match.
00050 #
00051 # @returns Whether the given string matches the given pattern.
00052 #
00053 # @retval 0 On match.
00054 # @retval 1 Otherwise.
00055 function match
00056 {
00057     [ $# -eq 2 ] || return 1
00058 
00059     local value=$1
00060     local pattern=$2
00061 
00062     if [ -z "${value}" ]; then
00063         [ -z "${pattern}" ]
00064     elif [ -z "${pattern}" ]; then
00065         [ -z "${value}" ]
00066     else
00067         if [ ${BASH_VERSION_MAJOR} -gt 2 ]; then
00068             # GNU bash, version 3.00.15(1)-release (x86_64-redhat-linux-gnu)
00069             # throws an error when a regular expression with groups
00070             # such as in '^(a|b|c)' is used. Here, quotes are required.
00071             if [ ${BASH_VERSION_MINOR} -eq 0 ]; then
00072                 [[ "${value}" =~ "${pattern}" ]]
00073             # GNU bash, version 3.2.25(1)-release (x86_64-redhat-linux-gnu)
00074             # works with either quotes or not. However, on Mac OS Snow Leopard,
00075             # GNU bash, version 3.2.48(1)-release (x86_64-apple-darwin10.0)
00076             # requires that no quotes are used. The quotes are otherwise
00077             # considered to be part of the pattern.
00078             else
00079                 [[ "${value}" =~ ${pattern} ]]
00080             fi
00081         else
00082             echo "${value}" | egrep -q "${pattern}"
00083         fi
00084     fi
00085 }
00086 
00087 # ============================================================================
00088 # upvar(s)
00089 # ============================================================================
00090 
00091 # ----------------------------------------------------------------------------
00092 ## @brief Assign variable one scope above the caller.
00093 #
00094 # This function can be used inside functions to return values by assigning
00095 # them to a variable in the scope of the caller.
00096 #
00097 # @note For assigning multiple variables, use upvars(). Do NOT use multiple
00098 #       upvar() calls, since one upvar() call might reassign a variable to
00099 #       be used by another upvar() call.
00100 #
00101 # Example:
00102 # @code
00103 # foo ()
00104 # {
00105 #     local "$1" && upvar $1 "Hello, World!"
00106 # }
00107 #
00108 # foo greeting
00109 # echo ${greeting}
00110 # @endcode
00111 #
00112 # @todo Under some circumstances, the 'local "$1" &&' part of the
00113 #       upvar() usage example has to be skipped. It is not yet clear what
00114 #       the correct solution/usage really is...
00115 #
00116 # @param [in] var    Variable name to assign value to
00117 # @param [in] values Value(s) to assign. If multiple values, an array is
00118 #                    assigned, otherwise a single value is assigned.
00119 #
00120 # @returns Nothing.
00121 #
00122 # @retval 0 On success.
00123 # @retval 1 On failure.
00124 #
00125 # @sa upvars()
00126 # @sa http://fvue.nl/wiki/Bash:_Passing_variables_by_reference
00127 function upvar
00128 {
00129     if unset -v "$1"; then           # Unset & validate varname
00130         if (( $# == 2 )); then
00131             eval $1=\"\$2\"          # Return single value
00132         else
00133             eval $1=\(\"\${@:2}\"\)  # Return array
00134         fi
00135     fi
00136 }
00137 
00138 # ----------------------------------------------------------------------------
00139 ## @brief Assign variables one scope above the caller.
00140 #
00141 # @par Synopsis
00142 # local varname [varname ...] && 
00143 # upvars [-v varname value] | [-aN varname [value ...]] ...
00144 #
00145 # @par Options:
00146 # - -aN  Assign next N values to varname as array
00147 # - -v   Assign single value to varname#
00148 #
00149 # @retval 0 On success.
00150 # @retval 1 On failure.
00151 #
00152 # @sa http://fvue.nl/wiki/Bash:_Passing_variables_by_reference
00153 function upvars
00154 {
00155     if ! (( $# )); then
00156         echo "${FUNCNAME[0]}: usage: ${FUNCNAME[0]} [-v varname"\
00157             "value] | [-aN varname [value ...]] ..." 1>&2
00158         return 2
00159     fi
00160     while (( $# )); do
00161         case $1 in
00162             -a*)
00163                 # Error checking
00164                 [[ ${1#-a} ]] || { echo "bash: ${FUNCNAME[0]}: \`$1': missing"\
00165                     "number specifier" 1>&2; return 1; }
00166                 printf %d "${1#-a}" &> /dev/null || { echo "bash:"\
00167                     "${FUNCNAME[0]}: \`$1': invalid number specifier" 1>&2
00168                     return 1; }
00169                 # Assign array of -aN elements
00170                 [[ "$2" ]] && unset -v "$2" && eval $2=\(\"\${@:3:${1#-a}}\"\) && 
00171                 shift $((${1#-a} + 2)) || { echo "bash: ${FUNCNAME[0]}:"\
00172                     "\`$1${2+ }$2': missing argument(s)" 1>&2; return 1; }
00173                 ;;
00174             -v)
00175                 # Assign single value
00176                 [[ "$2" ]] && unset -v "$2" && eval $2=\"\$3\" &&
00177                 shift 3 || { echo "bash: ${FUNCNAME[0]}: $1: missing"\
00178                 "argument(s)" 1>&2; return 1; }
00179                 ;;
00180             --help) echo "\
00181 Usage: local varname [varname ...] &&
00182    ${FUNCNAME[0]} [-v varname value] | [-aN varname [value ...]] ...
00183 Available OPTIONS:
00184 -aN VARNAME [value ...]   assign next N values to varname as array
00185 -v VARNAME value          assign single value to varname
00186 --help                    display this help and exit
00187 --version                 output version information and exit"
00188                 return 0 ;;
00189             --version) echo "\
00190 ${FUNCNAME[0]}-0.9.dev
00191 Copyright (C) 2010 Freddy Vulto
00192 License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
00193 This is free software: you are free to change and redistribute it.
00194 There is NO WARRANTY, to the extent permitted by law."
00195                 return 0 ;;
00196             *)
00197                 echo "bash: ${FUNCNAME[0]}: $1: invalid option" 1>&2
00198                 return 1 ;;
00199         esac
00200     done
00201 }
00202 
00203 # ============================================================================
00204 # quoted string <-> array
00205 # ============================================================================
00206 
00207 # ----------------------------------------------------------------------------
00208 ## @brief Build quoted string from array.
00209 #
00210 # Example:
00211 # @code
00212 # basis_array_to_quoted_string str 'this' "isn't" a 'simple example of "a quoted"' 'string'
00213 # echo "${str}"
00214 # @endcode
00215 #
00216 # @param [out] var      Name of result variable for quoted string.
00217 # @param [in]  elements All remaining arguments are considered to be the
00218 #                       elements of the array to convert.
00219 #
00220 # @returns Nothing.
00221 function basis_array_to_quoted_string
00222 {
00223     local i=0
00224     local str=''
00225     local element=''
00226     while [ $i -lt ${#basis_array[@]} ]; do
00227         element=${basis_array[$i]}
00228         # escape double quotes
00229         element=`echo -n "${element}" | sed "s/\"/\\\\\\\\\"/g"`
00230         # surround element by double quotes if it contains single quotes or whitespaces
00231         match "${element}" "[' ]" && element="\"${element}\""
00232         # append element
00233         [ -n "${str}" ] && str="${str} "
00234         str="${str}${element}"
00235         (( i++ ))
00236     done
00237     local "$1" && upvar $1 "${str}"
00238 }
00239 
00240 # ----------------------------------------------------------------------------
00241 ## @brief Split (quoted) string.
00242 #
00243 # This function can be used to split a (quoted) string into its elements.
00244 #
00245 # Example:
00246 # @code
00247 # str="'this' 'isn\'t' a \"simple example of \\\"a quoted\\\"\" 'string'"
00248 # basis_split array "${str}"
00249 # echo ${#array[@]}  # 5
00250 # echo "${array[3]}" # simple example of "a quoted"
00251 # @endcode
00252 #
00253 # @param [out] var Result variable for array.
00254 # @param [in]  str Quoted string.
00255 #
00256 # @returns Nothing.
00257 function basis_split
00258 {
00259     [ $# -eq 2 ] || return 1
00260     local _basis_split_str=$2
00261     # match arguments from left to right
00262     while match "${_basis_split_str}" "[ ]*('([^']|\\\')*[^\\]'|\"([^\"]|\\\")*[^\\]\"|[^ ]+)(.*)"; do
00263         # matched element including quotes
00264         _basis_split_element="${BASH_REMATCH[1]}"
00265         # remove quotes
00266         _basis_split_element=`echo "${_basis_split_element}" | sed "s/^['\"]//;s/['\"]$//"`
00267         # replace quoted quotes within argument by quotes
00268         _basis_split_element=`echo "${_basis_split_element}" | sed "s/[\\]'/'/g;s/[\\]\"/\"/g"`
00269         # add to resulting array
00270         _basis_split_array[${#_basis_split_array[@]}]="${_basis_split_element}"
00271         # continue with residual command-line
00272         _basis_split_str="${BASH_REMATCH[4]}"
00273     done
00274     # return
00275     # attention: using
00276     #local "$1" && upvar $1 "${_basis_split_array[@]}"
00277     # did not work
00278     upvar $1 "${_basis_split_array[@]}"
00279 }
00280 
00281 
00282 ## @}
00283 # end of Doxygen group