Skip to content

Commit 38a9e06

Browse files
authored
Merge pull request #42 from clelange/zsh_call_host
Support zsh in `call_host.sh`
2 parents 00fcfe4 + 2c6c7e0 commit 38a9e06

1 file changed

Lines changed: 160 additions & 25 deletions

File tree

call_host.sh

Lines changed: 160 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,94 @@ if [ -f "$CALL_HOST_CONFIG" ]; then
88
source "$CALL_HOST_CONFIG"
99
fi
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
1283
call_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
48127
fi
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
53155
call_host_enable(){
54156
export CALL_HOST_STATUS=enable
55157
}
56-
export -f call_host_enable
158+
export_func call_host_enable
57159
call_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
62164
call_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
71173
call_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

78180
call_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
119222
makepipe(){
@@ -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
128231
startpipe(){
@@ -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
138241
call_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
158264
copy_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

165278
if [ -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
204317
if [ -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

Comments
 (0)