@@ -8,15 +8,94 @@ if [ -f "$CALL_HOST_CONFIG" ]; then
88 source " $CALL_HOST_CONFIG "
99fi
1010
11+ # zsh / bash compatibility helpers
12+ is_zsh (){
13+ # detect the current shell process name (portable ps usage)
14+ if command -v ps > /dev/null 2>&1 ; then
15+ # get last path component if ps returns full path
16+ p=" $( ps -p $$ -o comm= 2> /dev/null | awk -F/ ' {print $NF}' ) "
17+ case " $p " in
18+ zsh) return 0 ;;
19+ bash) return 1 ;;
20+ esac
21+ fi
22+
23+ # fallback: check common names for $0 or $ZSH_NAME (login shells may have a leading dash)
24+ case " $( basename -- " ${ZSH_NAME:- $0 } " 2> /dev/null) " in
25+ zsh|-zsh) return 0 ;;
26+ esac
27+
28+ return 1
29+ }
30+
31+ if is_zsh; then
32+ # export a function to the environment for child shells (zsh)
33+ export_func (){
34+ typeset -fx " $1 " 2> /dev/null || true
35+ }
36+ # declare an associative array (zsh)
37+ declare_assoc (){
38+ typeset -A " $1 "
39+ }
40+ # get current function name in zsh (be tolerant if indices differ)
41+ current_funcname (){
42+ # Ensure standard zsh array indexing (1-based) regardless of user options
43+ emulate -L zsh
44+ # funcstack[1] is current function in zsh (1-indexed by default)
45+ # Handle potential edge cases with fallbacks
46+ printf ' %s' " ${funcstack[2]:- } "
47+ }
48+ # get function definition (zsh)
49+ get_function (){
50+ functions " $1 " 2> /dev/null
51+ }
52+ else
53+ # bash
54+ export_func (){
55+ [ -n " $1 " ] || return
56+ # shellcheck disable=SC2163
57+ export -f " $1 " 2> /dev/null || true
58+ }
59+ declare_assoc (){
60+ # create named associative array in bash
61+ declare -gA " $1 "
62+ }
63+ current_funcname (){
64+ # return the caller function name if available (FUNCNAME[1]), otherwise fall back to FUNCNAME[0]
65+ if [ -n " ${FUNCNAME[1]:- } " ]; then
66+ echo " ${FUNCNAME[1]} "
67+ else
68+ echo " ${FUNCNAME[0]:- } "
69+ fi
70+ }
71+ # get function definition (bash)
72+ get_function (){
73+ declare -f " $1 " 2> /dev/null
74+ }
75+ fi
76+
77+ # portable indirect variable access (works in both bash and zsh)
78+ getvar (){
79+ eval " printf '%s' \"\$ {$1 :-}\" "
80+ }
81+
1182# validation
1283call_host_valid (){
1384 VAR_TO_VALIDATE=" $1 "
14- # shellcheck disable=SC2076
15- if [[ ! " enable disable " =~ " ${! VAR_TO_VALIDATE} " ]]; then
16- echo " Warning: unsupported value ${! VAR_TO_VALIDATE} for $VAR_TO_VALIDATE ; disabling"
17- eval " export $VAR_TO_VALIDATE =disable"
18- fi
85+ # retrieve the value of the named variable
86+ VARVAL=" $( getvar " $VAR_TO_VALIDATE " ) "
87+ # check allowed values using portable case statement
88+ case " $VARVAL " in
89+ enable|disable)
90+ # valid value, do nothing
91+ ;;
92+ * )
93+ echo " Warning: unsupported value $VARVAL for $VAR_TO_VALIDATE ; disabling"
94+ eval " export $VAR_TO_VALIDATE =disable"
95+ ;;
96+ esac
1997}
98+ export_func call_host_valid
2099
21100# default values
22101: ${CALL_HOST_STATUS:= enable}
@@ -46,18 +125,41 @@ if [ ! -d "$CALL_HOST_DIR" ]; then
46125 echo " Warning: could not create specified dir CALL_HOST_DIR $CALL_HOST_DIR . disabling"
47126 export CALL_HOST_STATUS=disable
48127fi
49- # ensure the pipe dir is bound
50- export APPTAINER_BIND=${APPTAINER_BIND}${APPTAINER_BIND: +,}${CALL_HOST_DIR}
128+
129+ # helper: add value to a PATH-like variable only if not already present
130+ add_path_unique (){
131+ # args: varname value [sep]
132+ varname=" $1 " ; val=" $2 " ; sep=" ${3:-: } "
133+ # retrieve current value portably
134+ cur=" $( getvar " $varname " ) "
135+ # if empty, set and export
136+ if [ -z " $cur " ]; then
137+ eval " export $varname =\"\$ val\" "
138+ return
139+ fi
140+ # check for whole-element match using separators to avoid substrings
141+ case " ${sep}${cur}${sep} " in
142+ * " ${sep}${val}${sep} " * )
143+ # already present
144+ return
145+ ;;
146+ esac
147+ # append with separator
148+ eval " export $varname =\" ${cur}${sep}${val} \" "
149+ }
150+
151+ # ensure the pipe dir is bound (use comma separator for APPTAINER_BIND)
152+ add_path_unique APPTAINER_BIND " $CALL_HOST_DIR " " ,"
51153
52154# enable/disable toggles
53155call_host_enable (){
54156 export CALL_HOST_STATUS=enable
55157}
56- export -f call_host_enable
158+ export_func call_host_enable
57159call_host_disable (){
58160 export CALL_HOST_STATUS=disable
59161}
60- export -f call_host_disable
162+ export_func call_host_disable
61163# single toggle for debug printouts
62164call_host_debug (){
63165 if [ " $CALL_HOST_DEBUG " = " enable" ]; then
@@ -66,18 +168,19 @@ call_host_debug(){
66168 export CALL_HOST_DEBUG=enable
67169 fi
68170}
69- export -f call_host_debug
171+ export_func call_host_debug
70172# helper for debug printouts
71173call_host_debug_print (){
72174 if [ " $CALL_HOST_DEBUG " = " enable" ]; then
73175 echo " $@ "
74176 fi
75177}
76- export -f call_host_debug_print
178+ export_func call_host_debug_print
77179
78180call_host_plugin_01 (){
79181 # provide htcondor-specific info in container
80- declare -A CONDOR_OS
182+ # portable associative-array declaration
183+ declare_assoc CONDOR_OS
81184 CONDOR_OS[7]=" SL7"
82185 CONDOR_OS[8]=" EL8"
83186 CONDOR_OS[9]=" EL9"
@@ -93,7 +196,7 @@ call_host_plugin_01(){
93196 fi
94197 fi
95198}
96- export -f call_host_plugin_01
199+ export_func call_host_plugin_01
97200
98201# concept based on https://stackoverflow.com/questions/32163955/how-to-run-shell-script-on-host-from-docker-container
99202
@@ -113,7 +216,7 @@ listenhost(){
113216 echo " $tmpexit " > " $3 "
114217 done
115218}
116- export -f listenhost
219+ export_func listenhost
117220
118221# creates randomly named pipe and prints the name
119222makepipe (){
@@ -122,7 +225,7 @@ makepipe(){
122225 mkfifo " $PIPETMP "
123226 echo " $PIPETMP "
124227}
125- export -f makepipe
228+ export_func makepipe
126229
127230# to be run on host before launching each apptainer session
128231startpipe (){
@@ -132,16 +235,19 @@ startpipe(){
132235 # export pipes to apptainer
133236 echo " export APPTAINERENV_HOSTPIPE=$HOSTPIPE ; export APPTAINERENV_CONTPIPE=$CONTPIPE ; export APPTAINERENV_EXITPIPE=$EXITPIPE "
134237}
135- export -f startpipe
238+ export_func startpipe
136239
137240# sends function to host, then listens for output, and provides exit code from function
138241call_host (){
139242 # disable ctrl+c to prevent "Interrupted system call"
140243 trap " " SIGINT
141- if [ " ${FUNCNAME[0]} " = " call_host" ]; then
244+
245+ # determine caller function name in a portable way
246+ CURFN=" $( current_funcname) "
247+ if [ " $CURFN " = " call_host" ] || [ -z " $CURFN " ]; then
142248 FUNCTMP=
143249 else
144- FUNCTMP=" ${FUNCNAME[0]} "
250+ FUNCTMP=" $CURFN "
145251 fi
146252
147253 # extra environment settings; set every time because commands are executed on host in subshell
@@ -152,15 +258,22 @@ call_host(){
152258 cat < " $CONTPIPE "
153259 return " $( cat < " $EXITPIPE " ) "
154260}
155- export -f call_host
261+ export_func call_host
156262
157263# from https://stackoverflow.com/questions/1203583/how-do-i-rename-a-bash-function
158264copy_function () {
159- test -n " $( declare -f " $1 " ) " || return
160- eval " ${_/ $1 / $2 } "
161- eval " export -f $2 "
265+ # portable retrieval of function source and re-definition under a new name
266+ fnsrc=" $( get_function " $1 " ) "
267+ if [ -z " $fnsrc " ]; then
268+ return
269+ fi
270+ # replace only the first occurrence of the function name (at definition)
271+ # Use a more portable sed pattern without \b
272+ fnnew=" $( printf ' %s\n' " $fnsrc " | sed " 1s/^$1 /$2 /; 1s/^$1 ()/$2 ()/" ) "
273+ eval " $fnnew "
274+ export_func " $2 "
162275}
163- export -f copy_function
276+ export_func copy_function
164277
165278if [ -z " $APPTAINER_ORIG " ]; then
166279 export APPTAINER_ORIG=$( which apptainer)
@@ -198,11 +311,33 @@ apptainer(){
198311 )
199312 fi
200313}
201- export -f apptainer
314+ export_func apptainer
202315
203316# on host: get list of condor executables
204317if [ -z " $APPTAINER_CONTAINER " ]; then
205- export APPTAINERENV_HOSTFNS=$( compgen -c | grep ' ^condor_\|^eos' )
318+ # define command prefixes to search for
319+ HOSTFN_PREFIXES=" condor_ eos"
320+
321+ # portable command list discovery:
322+ if command -v compgen > /dev/null 2>&1 ; then
323+ # bash: use compgen with grep pattern built from prefixes
324+ GREP_PATTERN=" $( echo " $HOSTFN_PREFIXES " | sed ' s/ /|^/g' | sed ' s/^/^/' ) "
325+ export APPTAINERENV_HOSTFNS=$( compgen -c | grep -E " $GREP_PATTERN " | tr ' \n' ' ' )
326+ else
327+ # fallback: scan PATH for matching executables (portable)
328+ APPTAINERENV_HOSTFNS=" $( ( IFS=:
329+ for d in $PATH ; do
330+ [ -d " $d " ] || continue
331+ for prefix in $HOSTFN_PREFIXES ; do
332+ # shellcheck disable=SC2231
333+ for f in " $d " /${prefix} * ; do
334+ [ -e " $f " ] && [ -x " $f " ] && basename " $f "
335+ done
336+ done
337+ done ) | sort -u | tr ' \n' ' ' ) "
338+ export APPTAINERENV_HOSTFNS
339+ fi
340+
206341 if [ -n " $CALL_HOST_USERFNS " ]; then
207342 export APPTAINERENV_HOSTFNS=" $APPTAINERENV_HOSTFNS $CALL_HOST_USERFNS "
208343 fi
0 commit comments