Merge branch '408-modulecmd-module-avail-reveals-modules-which-are-actually-shadowed-by-an-overlay' into 'master'

Resolve "modulecmd: module avail reveals modules which are actually shadowed by an overlay"

Closes #408

See merge request Pmodules/src!440
This commit is contained in:
2025-03-21 14:15:03 +01:00
+205 -257
View File
@@ -371,9 +371,11 @@ get_module_config(){
- the release stage of other modules without a config file is
always "stable".
'
local -n ref_cfg="$1" # [out] reference to a dictionary to return the configuration
local -n ref_cfg="$1" # [out] reference to a dictionary to
# return the configuration
local -r dir="$2" # [in] directory containing modulefile
local -r modulefile="${dir}/$3" # [in] module name (inkl. version and/or sub-dirs)
local -r modulefile="${dir}/$3" # [in] module name (inkl. version
# and/or sub-dirs)
ref_cfg['relstage']='unstable'
ref_cfg['systems']=''
@@ -386,7 +388,8 @@ get_module_config(){
local -- group=''
find_overlay ol_name group "${modulefile}"
if [[ "${OverlayInfo[${ol_name}:layout]}" == 'Pmodules' ]]; then
[[ -r ${relstage_file} ]] && ref_cfg['relstage']=$( < "${relstage_file}" )
[[ -r ${relstage_file} ]] && \
ref_cfg['relstage']=$( < "${relstage_file}" )
else
ref_cfg['relstage']='stable'
fi
@@ -474,6 +477,7 @@ find_overlay () {
local -- path="$3" # [in] moduledir to check
path="${path%/"${__MODULEFILES_DIR__}"*}"
local -- ol=''
for ol in "${UsedOverlays[@]}"; do
local modulefiles_root="${OverlayInfo[${ol}:modulefiles_root]}"
if [[ "${path}" == ${modulefiles_root}/* ]]; then
@@ -641,8 +645,8 @@ set_lmfiles(){
declare -gx LOADEDMODULES=''
fi
local -- dir=''
local -a dirs=()
if [[ "${modulecmd}" == "${Lmod_cmd}" ]]; then
local -a dirs=()
IFS=':' read -r -a dirs <<<"${PmFiles}"
for dir in "${dirs[@]}"; do
std::append_path _LMFILES_ "${dir}"
@@ -654,7 +658,8 @@ set_lmfiles(){
# rebuild LOADEDMODULES by setting it to _LMFILES_ and then removing
# all directories given in MODULEPATH from LOADEDMODULES.
LOADEDMODULES="${_LMFILES_}"
while read -r dir; do
IFS=':' read -r -a dirs <<<"${MODULEPATH}"
for dir in "${dirs[@]}"; do
# If the first or last character of MODULEPATH is ':',
# we get an empty string. This shouldn't happen, but
# with this test we are on the save side.
@@ -672,7 +677,7 @@ set_lmfiles(){
# Remove this directory from all entries in LOADEDMODULES
LOADEDMODULES="${LOADEDMODULES//${dir}}"
done <<< "${MODULEPATH//:/$'\n'}"
done
}
##############################################################################
@@ -831,22 +836,23 @@ subcommand_load() {
subcommand_use "${relstage}"
fi
fi # handle extended module names
IFS=':' read -r -a modulepath <<< "${MODULEPATH}"
local moduledir=''
find_modulefile \
current_modulefile \
relstage \
moduledir \
modulecmd \
"${m}" \
"${modulepath[@]}"
find_modulefile current_modulefile relstage moduledir modulecmd "${m}" || {
local hints=''
get_load_hints hints
if [[ -z "${hints}" ]]; then
die_module_nexist "${m}"
else
die_module_unavail "${m}${hints}"
fi
}
# If the user wants to load/switch to another Pmodules version:
# This is possible if
# - no other module is loaded
# - the loaded version of Pmodules is >= 1.1.22
# - the to be loaded version of Pmodules is >= 1.1.22
if [[ ${m} == Pmodules/* ]]; then
# The user wants to load another Pmodules version!
# This is possible if
# - no other module is loaded
# - the loaded version of Pmodules is >= 1.1.22
# - the to be loaded version of Pmodules is >= 1.1.22
local -r new_version="${m##*/}"
[[ -v Version ]] || Version='0.0.0'
if [[ -n ${LOADEDMODULES} ]]; then
@@ -857,17 +863,20 @@ subcommand_load() {
Version="${new_version}"
fi
fi
# nothing to do if already loaded, continue with
# next module to be loaded
[[ ":${LOADEDMODULES}:" == *:${m}:* ]] && continue
interp[${current_modulefile}]="${modulecmd}"
# show info file if exist
# show info file
local prefix=''
get_module_prefix prefix "${current_modulefile}"
[[ -n ${prefix} && -r "${prefix}/.info" ]] && cat "${prefix}/.info" 1>&2
# loading the dependencies overwrites the interpreter for the
# currend modulefile, so we have to save it for later use.
interp[${current_modulefile}]="${modulecmd}"
# load dependencies
local -- deps_file="${current_modulefile%/*}/.deps-${current_modulefile##*/}"
if [[ ! -r "${deps_file}" && -n "${prefix}" ]]; then
@@ -875,21 +884,23 @@ subcommand_load() {
fi
test -r "${deps_file}" && load_dependencies "$_"
[[ ":${LOADEDMODULES}:" == *:${m}:* ]] && continue
# load module
modulecmd="${interp[${current_modulefile}]}"
local output=''
output=$("${modulecmd}" 'bash' "${opts[@]}" 'load' \
"${current_modulefile}" 2> "${TmpFile}")
# we do not want to print the error message we got from
# modulecmd, they are a bit ugly
# :FIXME: Not sure whether this is now correct!
# we don't print the error message we got from modulecmd, due to
# readability.
# :FIXME:
# Not sure whether this is now correct!
# The idea is to supress the error messages from the Tcl
# modulecmd, but not the output to stderr coded in a
# modulefile.
# :FIXME:
# Handle errors from Lmod.
# :FIXME:
# In some cases the error message is unclear.
local error=''
error=$( < "${TmpFile}")
if [[ "${error}" == *:ERROR:* ]]; then
@@ -1173,23 +1184,36 @@ get_available_modules() {
module_name_2
...
)'
local -n result="$1" # [out] reference variable to return result
local -r pattern="$2" # [in] search pattern
local -r relstages="$3" # |in] excepted release stages
local -n ref_modules="$4" # [in/out] dict. with available modules
local -n ref_modulenames="$5" # [in/out] dict. with available module names
shift 5
local -a dirs=("$@") # [in] module path (absolute directory names)
local -- mode="$1"
local -n result="$2" # [out] reference variable to return result
local -r pattern="$3" # [in] search pattern
local -r relstages="$4" # [in] excepted release stages
shift 4
local -a dirs=("$@") # [in] module path (absolute directory names)
local -A modules=()
local -A modulenames=()
local -- fpattern="${pattern}" # pattern used in find(1)
if [[ ${pattern} == */* ]]; then
# if pattern contains a slash, match against
# the part before the last slash.
# Example:
# If pattern is gcc/14, we have to match the string 'gcc'
# followed by a slash and a string not containing a slash.
fpattern=".*/${pattern%/*}/[^/]+"
else
# otherwise match max one slash
fpattern=".*/${pattern}[^/]*/*[^/]+"
fi
local -- dir=''
result=()
# loop over all entries in given module path
for dir in "${dirs[@]}"; do
test -d "${dir}" || continue
# find overlay and group for this directory
local ol=''
local group=''
find_overlay ol group "${dir}"
local -- ol_name=''
local -- group=''
find_overlay ol_name group "${dir}"
# loop over all files (and sym-links) in this directory and
# its sub-directories
@@ -1207,27 +1231,26 @@ get_available_modules() {
fi
[[ -n ${OverlayExcludes} \
&& "${mod}" =~ ${OverlayExcludes} ]] && continue
local name="${mod%/*}"
local add='no'
if [[ -n "${ol}" && "${ol,,}" != 'none' ]]; then
if [[ -n "${ol_name}" && "${ol_name,,}" != 'none' ]]; then
# module is in an overlay
#
# add to list of available modules, if
# - first time found by name only
# - in same overlay as first found
# - new version and not hidden by overlay
if [[ ! -v ref_modulenames[${name}] ]]; then
if [[ ! -v modulenames["${name}"] ]]; then
# new entry
if [[ "${OverlayInfo[${ol}:type]}" == "${ol_hiding}" ]]; then
ref_modulenames[${name}]="${ol}"
if [[ "${OverlayInfo[${ol_name}:type]}" == "${ol_hiding}" ]]; then
modulenames[${name}]="${ol_name}"
else
ref_modulenames[${name}]='0'
modulenames[${name}]='0'
fi
add='yes'
elif [[ "${ref_modulenames[${name}]}" == "${ol}" ]]; then
elif [[ "${modulenames[${name}]}" == "${ol_name}" ]]; then
add='yes'
elif [[ "${ref_modulenames[${name}]}" == '0' ]] \
&& [[ ! -v ref_modules[${mod}] ]]; then
elif [[ "${modulenames[${name}]}" == '0' ]] \
&& [[ ! -v modules[${mod}] ]]; then
add='yes'
fi
else
@@ -1235,13 +1258,22 @@ get_available_modules() {
add='yes' # module is NOT in an overlay
fi
[[ "${add}" == 'no' ]] && continue
if [[ "${mode}" == 'search' ]]; then
[[ "${mod}" =~ ${pattern} ]] || continue
else
if [[ "${pattern}" == */* ]]; then
[[ "${mod}" == ${pattern} ]] || continue
else
[[ "${mod%/*}" == ${pattern} ]] || continue
fi
fi
local -A cfg=()
local -- relstage='stable'
if [[ "${OverlayInfo[${ol}:layout]}" == 'Pmodules' ]]; then
if [[ "${OverlayInfo[${ol_name}:layout]}" == 'Pmodules' ]]; then
get_module_config cfg "${dir}" "${rel_modulefile}"
is_available cfg "${relstages}" || continue
relstage="${cfg['relstage']}"
elif [[ "${OverlayInfo[${ol}:layout]}" == 'Spack' ]]; then
elif [[ "${OverlayInfo[${ol_name}:layout]}" == 'Spack' ]]; then
if [[ ":${UsedReleaseStages}:" =~ :unstable: ]]; then
relstage='unstable'
fi
@@ -1249,15 +1281,17 @@ get_available_modules() {
get_module_config cfg "${dir}" "${rel_modulefile}"
is_available cfg "${ReleaseStages}" || continue
fi
result+=( "${mod}" "${relstage}" "${dir}" "${rel_modulefile}" "${ol}" "${group}" )
ref_modules[${mod}]=1
modules[${mod}]=1
result+=( "${mod}" "${relstage}" "${dir}" "${rel_modulefile}" "${ol_name}" "${group}" )
[[ "${mode}" != 'search' ]] && return 0
done < <(${find} -L "${dir}" \
-not -name ".*" \
\( -regex ".*/${pattern}[^/]+/[^/]+$" \
-o -regex ".*/${pattern}[^/]+$" -regextype posix-basic \) \
\( -regex "${fpattern}" \
-regextype posix-basic \) \
\( -type f -o -type l \) \
-printf "%P\n" \
| ${sort} --version-sort)
| ${sort} -r --version-sort)
done
} # get_available_modules()
@@ -1278,107 +1312,29 @@ find_modulefile(){
local -n ref_moduledir="$3"
local -n ref_interp="$4"
local -r modulename="$5"
local -a modulepath=()
_match(){
local -- found_modulefile="$1"
if [[ ${found_modulefile} == "${modulename}" ]]; then
return 0
fi
if [[ ${found_modulefile} == "${modulename}.lua" ]]; then
return 0
fi
if [[ ${modulename} != */* ]]; then
if [[ "${found_modulefile}" == "${modulename}"/* ]]; then
return 0
fi
fi
return 1
}
_find_modulefile() {
ref_modulefile=''
local -- dir=''
for dir in "${modulepath[@]}"; do
test -d "${dir}" || continue
local -i col=$((${#dir} + 2 ))
local -- mod=''
local -a found_modules=()
mapfile -t found_modules \
< <(${find} -L "${dir}" \
-type f \
-not -name '.*' \
\( -ipath "${dir}/${modulename}" \
-or -ipath "${dir}/${modulename}.lua" \
-or -ipath "${dir}/${modulename}/*" \) \
-printf "%P\n" \
| sort -rV \
)
for mod in "${found_modules[@]}"; do
_match "${mod}" || continue
if [[ -L "${dir}/${mod}" ]]; then
# handle symbolic link
# - resolve link to absolut path
# - the absolut path must be in ${dir}
# - if not: continue
# - else set module name to relativ path
# by removing ${dir}
local lname=''
lname=$( std::get_abspath "${dir}/${mod}" )
[[ "${lname}" == "${dir}/*" ]] || continue
mod="${lname/${dir}\/}"
fi
local -A cfg=()
local -- relstages="${UsedReleaseStages}"
get_module_config cfg "${dir}" "${mod}"
# if the full name is given, we don't care about
# release stages.
if [[ "${mod}" == "${modulename}" || \
"${mod}" == "${modulename}.lua" ]]; then
relstages="${ReleaseStages}"
fi
is_available cfg "${relstages}" || continue
ref_modulefile="${dir}/${mod}"
ref_relstage="${cfg['relstage']}"
ref_moduledir="${dir}"
return 0
done
done
# Nothing found in MODULEPATH!
# The module to be loaded must be either given as relative or absolut
# path.
if [[ -r "${modulename}" ]]; then
ref_modulefile="$(std::get_abspath "${modulename}")"
ref_relstage='stable'
ref_moduledir="$(${dirname} "${ref_modulefile}")"
return 0
fi
return 1
} # _find_modulefile()
if (( $# >= 6 )); then
modulepath=("${@:6}")
else
IFS=':' read -r -a modulepath <<< "${MODULEPATH}"
fi
if ! _find_modulefile; then
local hints=''
get_load_hints hints
if [[ -z "${hints}" ]]; then
die_module_nexist "${modulename}"
else
die_module_unavail "${modulename}${hints}"
fi
fi
is_modulefile modulecmd "${ref_modulefile}" || die_module_not_a_modulefile "${modulename}"
if [[ "${ref_interp}" == "${Lmod_cmd}" ]]; then
IFS=':' read -r -a modulepath <<<"${MODULEPATH}"
local -a mods=()
local -- relstages="${UsedReleaseStages}"
[[ "${modulename}" = */* ]] && relstages="${ReleaseStages}"
get_available_modules \
'load' \
mods \
"${modulename}" \
"${relstages}" \
"${modulepath[@]}"
(( ${#mods[@]} == 0 )) && return 1
ref_relstage="${mods[1]}"
ref_moduledir="${mods[2]}"
ref_modulefile="${mods[2]}/${mods[3]}"
is_modulefile ref_interp "${ref_modulefile}" || \
die_module_not_a_modulefile "${modulename}"
if [[ "${modulecmd}" == "${Lmod_cmd}" ]]; then
# Lmod doesn't support full qualified path names!
ref_modulefile="${ref_modulefile/${ref_moduledir}\/}"
fi
return 0
}
##############################################################################
@@ -1429,8 +1385,19 @@ subcommand_avail() {
[[ -t 1 && -t 2 ]] && cols=$(tput cols)
#......................................................................
output_header() {
local -r caption="$1"
local -i i=$1
# use group name, overlay name or directory
local -- caption="${mods[i+5]}" # group name
if [[ "${caption,,}" == 'none' ]]; then
caption="${mods[i+4]}" # overlay name
if [[ "${caption,,}" == 'none' ]]; then
caption="${mods[i+2]}" # directory
fi
fi
(( i != 0 )) && printf -- "\n\n" 1>&2
local -i i=0
(( i=(cols-${#caption})/2-2 ))
printf -- "%0.s-" $(seq 1 "$i") 1>&2
@@ -1441,9 +1408,19 @@ subcommand_avail() {
#......................................................................
terse_output() {
output_header "$1"
local -- cur_group=''
local -- cur_dir=''
local -i i=0
for (( i=0; i<${#mods[@]}; i+=6 )); do
if [[ "${cur_group}" != "${mods[i+5]}" ]] || \
[[ "${cur_group}" == 'none' && \
"${cur_dir}" != "${mods[i+2]}" ]]; then
output_header "$i"
cur_group="${mods[i+5]}"
cur_dir="${mods[i+2]}"
fi
local mod=${mods[i]%.lua}
local relstage=${mods[i+1]}
case ${relstage} in
@@ -1469,8 +1446,18 @@ subcommand_avail() {
#......................................................................
# :FIXME: for the time being, this is the same as terse_output!
long_output() {
output_header "$1"
local -- cur_group=''
local -- cur_dir=''
local -i i=0
for (( i=0; i<${#mods[@]}; i+=6 )); do
if [[ "${cur_group}" != "${mods[i+5]}" ]] || \
[[ "${cur_group}" == 'none' && \
"${cur_dir}" != "${mods[i+2]}" ]]; then
output_header "$i"
cur_group="${mods[i+5]}"
cur_dir="${mods[i+2]}"
fi
local mod=${mods[i]%.lua}
local relstage=${mods[i+1]}
case ${relstage} in
@@ -1488,12 +1475,22 @@ subcommand_avail() {
#......................................................................
human_readable_output() {
output_header "$1"
local -a available_modules=()
local -- cur_group=''
local -- cur_dir=''
local mod=''
local -i max_length=1
local -i colsize=16
local -i column=$cols # force a line-break
for ((i=0; i<${#mods[@]}; i+=6)); do
# print header if
# - module is in another group or overlay
# - group == none && overlay == none and module is in another dir
if [[ "${cur_group}" != "${mods[i+5]}" ]] || \
[[ "${cur_group}" == 'none' && \
"${cur_dir}" != "${mods[i+2]}" ]]; then
output_header "$i"
cur_group="${mods[i+5]}"
cur_dir="${mods[i+2]}"
fi
if [[ ${Verbosity_lvl} == 'verbose' ]]; then
local relstage=${mods[i+1]}
case ${relstage} in
@@ -1507,25 +1504,20 @@ subcommand_avail() {
else
mod=${mods[i]}
fi
local -i n=${#mod}
(( n > max_length )) && (( max_length=n ))
available_modules+=("${mod}")
done
local -i span=$(( max_length / 16 + 1 )) # compute column size
local -i colsize=$(( span * 16 )) # as multiple of 16
local -i column=$cols # force a line-break
for mod in "${available_modules[@]}"; do
local -i len=${#mod}
if (( column+len >= cols )); then
printf -- "\n" 1>&2
column=0
fi
if (( column+colsize < cols )); then
printf "%-${colsize}s" "${mod}" 1>&2
local -i size=0
(( size=((len)/colsize+1)*colsize ))
if (( column+size < cols )); then
printf "%-${size}s" "${mod}" 1>&2
else
printf "%-s" "${mod}" 1>&2
fi
column+=colsize
column+=size
done
printf -- "\n\n" 1>&2
}
@@ -1579,81 +1571,43 @@ subcommand_avail() {
if (( ${#pattern[@]} == 0 )); then
pattern+=( '' )
fi
# With overlays we can have multiple directories per group!
# To find the modules in a given group, we have to loop over
# these directories. In the for loop below, we create a
# 'modulepath' per group and a list of groups. We loop over
# this list of groups in the second for-loop.
local -- dir=''
local -- group=''
local -- groups=()
local -- groups=''
local -- ol=''
local -- name=''
local -a modulepath=()
local -A modulepath_of_group=()
IFS=':' read -r -a modulepath <<<"${MODULEPATH}"
for dir in "${modulepath[@]}"; do
if find_overlay ol group "${dir}"; then
name="${group}"
else
name="${dir}"
group="${dir//[\/ .-]/_}"
find_overlay ol group "${dir}" || group="${dir}"
[[ -v modulepath_of_group[${group}] ]] || modulepath_of_group[${group}]=''
typeset -n tmp="modulepath_of_group[${group}]"
std::append_path tmp "${dir}"
if (( ${#opt_groups[@]} > 0 )); then
# add only groups specified on the command line
[[ -v opt_groups[${group}] ]] || continue
fi
# With overlays we can have multiple directories per group!
#
# Create an ordered list of directories per group.
# Note:
# BASH doesn't support list as values of dictionaries. We use
# reference variables to work around this.
if [[ ! -v modulepath_${group} ]]; then
typeset -a "modulepath_${group}"
fi
typeset -n path="modulepath_${group}"
path+=("${dir}")
# Create ordered list of groups. In the next step we
# loop over this list.
for group in "${groups[@]}"; do
if [[ "${group}" == "${name}" ]]; then
# resume with next dir
continue 2
fi
done
groups+=( "${name}" )
std::append_path groups "${group}"
done
local -A found_modules=()
local -A found_modulenames=()
local string
local -a modulepath=()
for group in ${groups//:/ }; do
modulepath+="${modulepath_of_group[${group}]}:"
done
local -a path=()
IFS=':' read -r -a path <<<"${modulepath%:}"
for string in "${pattern[@]}"; do
for group in "${groups[@]}"; do
# limit output to certain groups
if (( ${#opt_groups[@]} > 0 )) && [[ ! -v opt_groups[${group}] ]]; then
continue
fi
# replace the characters '/', ' ', '.' and '-' with underscore
local ref="${group//[\/ .-]/_}"
# continue if module path for this group is empty
[[ -v modulepath_${ref} ]] || continue
typeset -n path"=modulepath_${ref}"
local -- header_text=''
local -- ol="${UsedOverlays[0]}"
if [[ "${OverlayInfo[${ol}:layout]}" == 'Pmodules' ]]; then
found_modules=()
found_modulenames=()
fi
get_available_modules \
mods \
"${string}*" \
"${opt_use_relstages}" \
found_modules \
found_modulenames \
"${path[@]}"
[[ ${#mods[@]} == 0 ]] && continue
if [[ "${group,,}" == 'none' ]]; then
# if we have no groups, the overlay is the
# same for all modules in ${mods[@]}. So we
# can use the overlay of the first module.
header_text="${mods[4]}"
else
header_text="${group}"
fi
${output_function} "${header_text}"
done
get_available_modules \
'search' \
mods \
"${string}" \
"${opt_use_relstages}" \
"${path[@]}"
${output_function}
done
} # subcommand_avail()
@@ -2680,7 +2634,6 @@ subcommand_search() {
local opt_use_relstages=':'
local opt_all_deps='no'
local opt_wrap='no'
local opt_glob='no'
local opt_newest='no'
#.....................................................................
@@ -2826,15 +2779,12 @@ subcommand_search() {
# get and print all available modules in $mpath
# with respect to the requested release stage
# TmpFile: module/version relstage group dependencies...
local -- mods=''
local -A found_modules=()
local -A found_modulenames=()
local -a mods=()
get_available_modules \
'search' \
mods \
"${module}" \
"${opt_use_relstages}" \
found_modules \
found_modulenames \
"${modulepath[@]}"
local i=0
for (( i=0; i<${#mods[@]}; i+=6 )); do
@@ -2850,7 +2800,6 @@ subcommand_search() {
if (( ${#name} > max_len_modulename)); then
max_len_modulename=${#name}
fi
if [[ "${OverlayInfo[${ol}:layout]}" == 'Pmodules' ]]; then
if [[ "${opt_print_verbose}" == 'yes' ]] || \
[[ "${opt_all_deps}" == 'yes' ]]; then
@@ -2873,11 +2822,13 @@ subcommand_search() {
unset IFS
fi
elif [[ "${OverlayInfo[${ol}:layout]}" == 'Spack' ]]; then
IFS='/' read -r -a toks <<< "${rel_modulefile}"
local -i j=0
for ((j = 0; j < ${#toks[@]}-2; j+=2)); do
deps+=( "${toks[$j]}/${toks[$j+1]}" );
done
if [[ "${rel_modulefile}" != Core/* ]]; then
IFS='/' read -r -a toks <<< "${rel_modulefile}"
local -i j=0
for ((j = 0; j < ${#toks[@]}-2; j+=2)); do
deps+=( "${toks[$j]}/${toks[$j+1]}" );
done
fi
fi
echo "${name}" "${relstage}" "${group}" "${modulefile}" \
"${ol}" \
@@ -2940,9 +2891,6 @@ subcommand_search() {
--wrap )
opt_wrap='yes'
;;
--glob )
opt_glob='yes'
;;
--newest )
opt_newest='yes'
;;
@@ -2964,7 +2912,6 @@ subcommand_search() {
local -a groups=( "${!GroupDepths[@]}" )
local -- module=''
for module in "${modules[@]}"; do
[[ ${opt_glob} == 'no' ]] && module+="*"
local -a modulepath=()
# search in overlays with layout 'Spack'
@@ -2982,11 +2929,12 @@ subcommand_search() {
fi
[[ -z "${path}" ]] && path="${OverlayInfo[${ol_name}:modulepath]}"
[[ -z "${path}" ]] && continue
local -a modulepath=()
IFS=':' read -r -a modulepath <<<"${path}"
local -a mpath=()
IFS=':' read -r -a mpath <<<"${path}"
local -- dir=''
# remove last directory
for dir in "${modulepath[@]}"; do
for dir in "${mpath[@]}"; do
modulepath+=( "${dir%/*}" )
done
done