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