core.sh
Go to the documentation of this file.
1 # ============================================================================
2 # Copyright (c) 2011-2012 University of Pennsylvania
3 # Copyright (c) 2013-2016 Andreas Schuh
4 # All rights reserved.
5 #
6 # See COPYING file for license information or visit
7 # https://cmake-basis.github.io/download.html#license
8 # ============================================================================
9 
10 ##############################################################################
11 # @file core.sh
12 # @brief Core functions for Bash development.
13 #
14 # This is the core module of the BASIS utilities for Bash. It implements
15 # fundamental functions for the development in Bash. Therefore, this module
16 # has to be kept independent of any other modules and shall only make use
17 # of Bash builtin's and basic commands.
18 ##############################################################################
19 
20 [ "${_BASIS_CORE_INCLUDED}" == 'true' ] || {
21 _BASIS_CORE_INCLUDED='true'
22 
23 
24 . "`cd -P -- \`dirname -- "${BASH_SOURCE}"\` && pwd`/config.sh" || exit 1
25 
26 
27 ## @addtogroup BasisBashUtilities
28 # @{
29 
30 
31 # ============================================================================
32 # module import
33 # ============================================================================
34 
35 # ----------------------------------------------------------------------------
36 ## @brief Import Bash module.
37 #
38 # This function can be used to import, i.e., source, a named module, where the
39 # module is searched for in the directories specified by the @c BASHPATH
40 # (environment) variable. Modules can be prepended by the names of the
41 # subdirectories they are located in relative to a directory listed in the
42 # @c BASHPATH, where dots (.) can be used instead of slashes to separate the
43 # parts of the relative module path. A <tt>.sh</tt> file name extension of the
44 # modules is appended to the module name when looking for a matching Bash
45 # module file. It must not be included in the \p module name to avoid
46 # confusion with the dot used as path / submodule separator. The module name
47 # "utils.sh" thus translates to the file path "utils/sh.sh"!
48 #
49 # Example:
50 # @code
51 # import sbia.basis.core
52 # import -o optional.module
53 # if [ $? -eq 0 ]; then
54 # echo "Module optional.module imported successfully"
55 # else
56 # echo "Module optional.module not found"
57 # fi
58 # @endcode
59 #
60 # @note The directories listed in the @c BASHPATH must be absolute paths
61 # starting with a forward slash (/).
62 #
63 # @param [in] options Option flags. The only possible option is -o. If this
64 # option is given before the module name, the function
65 # returns with return value 1 on error. Otherwise, it
66 # calls the exit function with this exit code.
67 # It can therefore be used for optional modules, which
68 # do not necessarily need to be imported.
69 # @param [in] module Name of the module to import (excl. .sh file name extension).
70 #
71 # @returns Only on success or if the -o option is given. Otherwise it calls
72 # the exit function if an error occurs.
73 #
74 # @retval 0 if the module was loaded successfully.
75 # @retval 1 on error if -o option is given.
76 import()
77 {
78  if [[ -z "${BASHPATH}" ]]; then
79  echo "import: BASHPATH not specified!" 1>&2
80  exit 1
81  fi
82  local exitonerror='true'
83  while [ $# -gt 0 ]; do
84  case "$1" in
85  -o) exitonerror='false'; ;;
86  *) break; ;;
87  esac
88  shift
89  done
90  local module="${1//.//}" # replace dots (.) by forward slashes (/)
91  if [[ -z "${module}" ]]; then
92  echo "import: missing module name argument!" 1>&2
93  exit 1
94  fi
95  if [[ $# -gt 1 ]]; then
96  echo "import: too many arguments!" 1>&2
97  exit 1
98  fi
99  [[ ${module: -3} == '.sh' ]] || module="${module}.sh"
100  local path="${BASHPATH}:" # ATTENTION: Trailing ':' required to terminate while loop!
101  local root=
102  while [[ -n "${path}" ]]; do
103  root="${path%%:*}"
104  if [[ "${root:0:1}" == '/' && -f "${root}/${module}" ]]; then
105  . "${root}/${module}"
106  return 0
107  fi
108  path="${path#*:}"
109  done
110  if [[ ${exitonerror} == 'true' ]]; then
111  echo "import: module '$1' not found!" 1>&2
112  exit 1
113  fi
114  return 1
115 }
116 
117 # ============================================================================
118 # pattern matching
119 # ============================================================================
120 
121 # ----------------------------------------------------------------------------
122 ## @brief This function implements a more portable way to do pattern matching.
123 #
124 # Unfortunately, there are significant differences in the way patterns have
125 # to be matched when using different shells. This function considers which
126 # shell is used (at the moment only BASH), and uses the appropriate syntax
127 # for the pattern matching.
128 #
129 # @param [in] value The string to match against pattern.
130 # @param [in] pattern The pattern to match.
131 #
132 # @returns Whether the given string matches the given pattern.
133 #
134 # @retval 0 On match.
135 # @retval 1 Otherwise.
136 match()
137 {
138  [ $# -eq 2 ] || return 1
139 
140  local value=$1
141  local pattern=$2
142 
143  if [ -z "${value}" ]; then
144  [ -z "${pattern}" ]
145  elif [ -z "${pattern}" ]; then
146  [ -z "${value}" ]
147  else
148  if [ ${BASH_VERSION_MAJOR} -gt 2 ]; then
149  # GNU bash, version 3.00.15(1)-release (x86_64-redhat-linux-gnu)
150  # throws an error when a regular expression with groups
151  # such as in '^(a|b|c)' is used. Here, quotes are required.
152  if [ ${BASH_VERSION_MAJOR} -eq 3 -a ${BASH_VERSION_MINOR} -eq 0 ]; then
153  [[ "${value}" =~ "${pattern}" ]]
154  # GNU bash, version 3.2.25(1)-release (x86_64-redhat-linux-gnu)
155  # works with either quotes or not. However, on Mac OS Snow Leopard,
156  # GNU bash, version 3.2.48(1)-release (x86_64-apple-darwin10.0)
157  # requires that no quotes are used. The quotes are otherwise
158  # considered to be part of the pattern.
159  else
160  [[ "${value}" =~ ${pattern} ]]
161  fi
162  else
163  echo "${value}" | egrep -q "${pattern}"
164  fi
165  fi
166 }
167 
168 # ============================================================================
169 # upvar(s)
170 # ============================================================================
171 
172 # ----------------------------------------------------------------------------
173 ## @brief Assign variable one scope above the caller.
174 #
175 # This function can be used inside functions to return values by assigning
176 # them to a variable in the scope of the caller.
177 #
178 # @note For assigning multiple variables, use upvars(). Do NOT use multiple
179 # upvar() calls, since one upvar() call might reassign a variable to
180 # be used by another upvar() call.
181 #
182 # Example:
183 # @code
184 # foo ()
185 # {
186 # local "$1" && upvar $1 "Hello, World!"
187 # }
188 #
189 # foo greeting
190 # echo ${greeting}
191 # @endcode
192 #
193 # @param [in] var Variable name to assign value to
194 # @param [in] values Value(s) to assign. If multiple values, an array is
195 # assigned, otherwise a single value is assigned.
196 #
197 # @returns Nothing.
198 #
199 # @retval 0 On success.
200 # @retval 1 On failure.
201 #
202 # @sa upvars()
203 # @sa http://fvue.nl/wiki/Bash:_Passing_variables_by_reference
204 upvar()
205 {
206  if unset -v "$1"; then # Unset & validate varname
207  if (( $# == 2 )); then
208  eval $1=\"\$2\" # Return single value
209  else
210  eval $1=\(\"\${@:2}\"\) # Return array
211  fi
212  fi
213 }
214 
215 # ----------------------------------------------------------------------------
216 ## @brief Assign variables one scope above the caller.
217 #
218 # @par Synopsis
219 # local varname [varname ...] &&
220 # upvars [-v varname value] | [-aN varname [value ...]] ...
221 #
222 # @par Options:
223 # - -aN Assign next N values to varname as array
224 # - -v Assign single value to varname#
225 #
226 # @retval 0 On success.
227 # @retval 1 On failure.
228 #
229 # @sa http://fvue.nl/wiki/Bash:_Passing_variables_by_reference
230 upvars()
231 {
232  if ! (( $# )); then
233  echo "${FUNCNAME[0]}: usage: ${FUNCNAME[0]} [-v varname"\
234  "value] | [-aN varname [value ...]] ..." 1>&2
235  return 2
236  fi
237  while (( $# )); do
238  case $1 in
239  -a*)
240  # Error checking
241  [[ ${1#-a} ]] || { echo "bash: ${FUNCNAME[0]}: \`$1': missing"\
242  "number specifier" 1>&2; return 1; }
243  printf %d "${1#-a}" &> /dev/null || { echo "bash:"\
244  "${FUNCNAME[0]}: \`$1': invalid number specifier" 1>&2
245  return 1; }
246  # Assign array of -aN elements
247  [[ "$2" ]] && unset -v "$2" && eval $2=\(\"\${@:3:${1#-a}}\"\) &&
248  shift $((${1#-a} + 2)) || { echo "bash: ${FUNCNAME[0]}:"\
249  "\`$1${2+ }$2': missing argument(s)" 1>&2; return 1; }
250  ;;
251  -v)
252  # Assign single value
253  [[ "$2" ]] && unset -v "$2" && eval $2=\"\$3\" &&
254  shift 3 || { echo "bash: ${FUNCNAME[0]}: $1: missing"\
255  "argument(s)" 1>&2; return 1; }
256  ;;
257  --help) echo "\
258 Usage: local varname [varname ...] &&
259  ${FUNCNAME[0]} [-v varname value] | [-aN varname [value ...]] ...
260 Available OPTIONS:
261 -aN VARNAME [value ...] assign next N values to varname as array
262 -v VARNAME value assign single value to varname
263 --help display this help and exit
264 --version output version information and exit"
265  return 0 ;;
266  --version) echo "\
267 ${FUNCNAME[0]}-0.9.dev
268 Copyright (C) 2010 Freddy Vulto
269 License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
270 This is free software: you are free to change and redistribute it.
271 There is NO WARRANTY, to the extent permitted by law."
272  return 0 ;;
273  *)
274  echo "bash: ${FUNCNAME[0]}: $1: invalid option" 1>&2
275  return 1 ;;
276  esac
277  done
278 }
279 
280 
281 ## @}
282 # end of Doxygen group
283 
284 
285 } # _BASIS_CORE_INCLUDED
if(oldcoutbuf)