From b3f1d07940fbec283b26d45b89c224e353b11c44 Mon Sep 17 00:00:00 2001 From: Clemens Lange Date: Wed, 29 Oct 2025 14:42:00 +0100 Subject: [PATCH 1/5] Make call_host.sh work with zsh --- call_host.sh | 109 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 88 insertions(+), 21 deletions(-) diff --git a/call_host.sh b/call_host.sh index 3535b12..2677566 100755 --- a/call_host.sh +++ b/call_host.sh @@ -8,15 +8,47 @@ if [ -f "$CALL_HOST_CONFIG" ]; then source "$CALL_HOST_CONFIG" fi +# zsh / bash compatibility helpers +if [ -n "$ZSH_VERSION" ]; then + # export a function to the environment for child shells (zsh) + export_func(){ + typeset -fx "$1" 2>/dev/null || true + } + # declare an associative array (zsh) - create a named array using eval so dynamic name works + declare_assoc(){ + eval "typeset -A $1" + } + # get current function name in zsh (be tolerant if indices differ) + current_funcname(){ + echo "${funcstack[1]:-${funcstack[0]:-}}" + } +else + # bash + export_func(){ + export -f "$1" 2>/dev/null || true + } + declare_assoc(){ + # create named associative array in bash + eval "declare -A $1" + } + current_funcname(){ + # return the current function name (FUNCNAME[0]) in bash + echo "${FUNCNAME[0]:-}" + } +fi + # validation call_host_valid(){ VAR_TO_VALIDATE="$1" - # shellcheck disable=SC2076 - if [[ ! " enable disable " =~ " ${!VAR_TO_VALIDATE} " ]]; then - echo "Warning: unsupported value ${!VAR_TO_VALIDATE} for $VAR_TO_VALIDATE; disabling" + # retrieve the value of the named variable in a way that works in bash and zsh + VARVAL="$(eval "printf '%s' \"\${$VAR_TO_VALIDATE}\"")" + # check allowed values in a POSIX-compatible way + if [ "$VARVAL" != "enable" ] && [ "$VARVAL" != "disable" ]; then + echo "Warning: unsupported value $VARVAL for $VAR_TO_VALIDATE; disabling" eval "export $VAR_TO_VALIDATE=disable" fi } +export_func call_host_valid # default values : ${CALL_HOST_STATUS:=enable} @@ -53,11 +85,11 @@ export APPTAINER_BIND=${APPTAINER_BIND}${APPTAINER_BIND:+,}${CALL_HOST_DIR} call_host_enable(){ export CALL_HOST_STATUS=enable } -export -f call_host_enable +export_func call_host_enable call_host_disable(){ export CALL_HOST_STATUS=disable } -export -f call_host_disable +export_func call_host_disable # single toggle for debug printouts call_host_debug(){ if [ "$CALL_HOST_DEBUG" = "enable" ]; then @@ -66,18 +98,19 @@ call_host_debug(){ export CALL_HOST_DEBUG=enable fi } -export -f call_host_debug +export_func call_host_debug # helper for debug printouts call_host_debug_print(){ if [ "$CALL_HOST_DEBUG" = "enable" ]; then echo "$@" fi } -export -f call_host_debug_print +export_func call_host_debug_print call_host_plugin_01(){ # provide htcondor-specific info in container - declare -A CONDOR_OS + # portable associative-array declaration + declare_assoc CONDOR_OS CONDOR_OS[7]="SL7" CONDOR_OS[8]="EL8" CONDOR_OS[9]="EL9" @@ -93,7 +126,7 @@ call_host_plugin_01(){ fi fi } -export -f call_host_plugin_01 +export_func call_host_plugin_01 # concept based on https://stackoverflow.com/questions/32163955/how-to-run-shell-script-on-host-from-docker-container @@ -113,7 +146,7 @@ listenhost(){ echo "$tmpexit" > "$3" done } -export -f listenhost +export_func listenhost # creates randomly named pipe and prints the name makepipe(){ @@ -122,7 +155,7 @@ makepipe(){ mkfifo "$PIPETMP" echo "$PIPETMP" } -export -f makepipe +export_func makepipe # to be run on host before launching each apptainer session startpipe(){ @@ -132,16 +165,19 @@ startpipe(){ # export pipes to apptainer echo "export APPTAINERENV_HOSTPIPE=$HOSTPIPE; export APPTAINERENV_CONTPIPE=$CONTPIPE; export APPTAINERENV_EXITPIPE=$EXITPIPE" } -export -f startpipe +export_func startpipe # sends function to host, then listens for output, and provides exit code from function call_host(){ # disable ctrl+c to prevent "Interrupted system call" trap "" SIGINT - if [ "${FUNCNAME[0]}" = "call_host" ]; then + + # determine caller function name in a portable way + CURFN="$(current_funcname)" + if [ "$CURFN" = "call_host" ] || [ -z "$CURFN" ]; then FUNCTMP= else - FUNCTMP="${FUNCNAME[0]}" + FUNCTMP="$CURFN" fi # extra environment settings; set every time because commands are executed on host in subshell @@ -152,15 +188,32 @@ call_host(){ cat < "$CONTPIPE" return "$(cat < "$EXITPIPE")" } -export -f call_host +export_func call_host # from https://stackoverflow.com/questions/1203583/how-do-i-rename-a-bash-function copy_function() { - test -n "$(declare -f "$1")" || return - eval "${_/$1/$2}" - eval "export -f $2" + # portable retrieval of function source and re-definition under a new name + if [ -n "$ZSH_VERSION" ]; then + # zsh: use `functions` to get the definition + if functions "$1" >/dev/null 2>&1; then + fnsrc="$(functions "$1" 2>/dev/null)" + else + return + fi + else + # bash: use declare -f + if declare -f "$1" >/dev/null 2>&1; then + fnsrc="$(declare -f "$1")" + else + return + fi + fi + # replace the function name in the source and eval it to create new function + fnnew="$(printf '%s\n' "$fnsrc" | sed "s/^$1\\b/$2/")" + eval "$fnnew" + export_func "$2" } -export -f copy_function +export_func copy_function if [ -z "$APPTAINER_ORIG" ]; then export APPTAINER_ORIG=$(which apptainer) @@ -198,11 +251,25 @@ apptainer(){ ) fi } -export -f apptainer +export_func apptainer # on host: get list of condor executables if [ -z "$APPTAINER_CONTAINER" ]; then - export APPTAINERENV_HOSTFNS=$(compgen -c | grep '^condor_\|^eos') + # portable command list discovery: + if command -v compgen >/dev/null 2>&1; then + export APPTAINERENV_HOSTFNS=$(compgen -c | grep -E '^condor_|^eos' | tr '\n' ' ') + else + # fallback: scan PATH for matching executables (portable) + APPTAINERENV_HOSTFNS="$( ( IFS=: + for d in $PATH; do + [ -d "$d" ] || continue + for f in "$d"/condor_* "$d"/eos*; do + [ -x "$f" ] && basename "$f" + done + done ) | sort -u | tr '\n' ' ')" + export APPTAINERENV_HOSTFNS + fi + if [ -n "$CALL_HOST_USERFNS" ]; then export APPTAINERENV_HOSTFNS="$APPTAINERENV_HOSTFNS $CALL_HOST_USERFNS" fi From 6d7d4ec2c1c34d4896aeadd1e346719727133d69 Mon Sep 17 00:00:00 2001 From: Clemens Lange Date: Wed, 29 Oct 2025 14:56:04 +0100 Subject: [PATCH 2/5] Fix zsh check to work also when running bash within apptainer started from zsh --- call_host.sh | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/call_host.sh b/call_host.sh index 2677566..c98682c 100755 --- a/call_host.sh +++ b/call_host.sh @@ -9,7 +9,25 @@ if [ -f "$CALL_HOST_CONFIG" ]; then fi # zsh / bash compatibility helpers -if [ -n "$ZSH_VERSION" ]; then +is_zsh(){ + # detect the current shell process name (portable ps usage) + if command -v ps >/dev/null 2>&1; then + # get last path component if ps returns full path + p="$(ps -p $$ -o comm= 2>/dev/null | awk -F/ '{print $NF}')" + case "$p" in + zsh) return 0 ;; + esac + fi + + # fallback: check common names for $0 or $ZSH_NAME (login shells may have a leading dash) + case "$(basename -- "${ZSH_NAME:-$0}" 2>/dev/null)" in + zsh|-zsh) return 0 ;; + esac + + return 1 +} + +if is_zsh; then # export a function to the environment for child shells (zsh) export_func(){ typeset -fx "$1" 2>/dev/null || true @@ -193,7 +211,7 @@ export_func call_host # from https://stackoverflow.com/questions/1203583/how-do-i-rename-a-bash-function copy_function() { # portable retrieval of function source and re-definition under a new name - if [ -n "$ZSH_VERSION" ]; then + if is_zsh; then # zsh: use `functions` to get the definition if functions "$1" >/dev/null 2>&1; then fnsrc="$(functions "$1" 2>/dev/null)" From 3650254ec81f5c8c332bdc0d25a871451257a49b Mon Sep 17 00:00:00 2001 From: Clemens Lange Date: Wed, 29 Oct 2025 15:20:56 +0100 Subject: [PATCH 3/5] Update the bash current_funcname helper to return the caller name (FUNCNAME[1]) instead of the helper's own name. --- call_host.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/call_host.sh b/call_host.sh index c98682c..1956415 100755 --- a/call_host.sh +++ b/call_host.sh @@ -1,4 +1,5 @@ #!/bin/bash +# Print each command before executing it (for debugging) # shellcheck disable=SC2155,SC2223 # check for configuration @@ -50,8 +51,12 @@ else eval "declare -A $1" } current_funcname(){ - # return the current function name (FUNCNAME[0]) in bash - echo "${FUNCNAME[0]:-}" + # return the caller function name if available (FUNCNAME[1]), otherwise fall back to FUNCNAME[0] + if [ -n "${FUNCNAME[1]:-}" ]; then + echo "${FUNCNAME[1]}" + else + echo "${FUNCNAME[0]:-}" + fi } fi From f93eba47be3536d496559a93f703f5f064564519 Mon Sep 17 00:00:00 2001 From: Clemens Lange Date: Wed, 29 Oct 2025 16:53:20 +0100 Subject: [PATCH 4/5] Add unique path function --- call_host.sh | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/call_host.sh b/call_host.sh index 1956415..c9ae0d8 100755 --- a/call_host.sh +++ b/call_host.sh @@ -101,8 +101,31 @@ if [ ! -d "$CALL_HOST_DIR" ]; then echo "Warning: could not create specified dir CALL_HOST_DIR $CALL_HOST_DIR. disabling" export CALL_HOST_STATUS=disable fi -# ensure the pipe dir is bound -export APPTAINER_BIND=${APPTAINER_BIND}${APPTAINER_BIND:+,}${CALL_HOST_DIR} + +# helper: add value to a PATH-like variable only if not already present +add_path_unique(){ + # args: varname value [sep] + varname="$1"; val="$2"; sep="${3:-:}" + # retrieve current value portably + cur="$(eval "printf '%s' \"\${${varname}:-}\"")" + # if empty, set and export + if [ -z "$cur" ]; then + eval "export $varname=\"\$val\"" + return + fi + # check for whole-element match using separators to avoid substrings + case "${sep}${cur}${sep}" in + *"${sep}${val}${sep}"*) + # already present + return + ;; + esac + # append with separator + eval "export $varname=\"${cur}${sep}${val}\"" +} + +# ensure the pipe dir is bound (use comma separator for APPTAINER_BIND) +add_path_unique APPTAINER_BIND "$CALL_HOST_DIR" "," # enable/disable toggles call_host_enable(){ From ad93699880e771e77f1e9bafca662a691617a359 Mon Sep 17 00:00:00 2001 From: Clemens Lange Date: Wed, 29 Oct 2025 22:37:11 +0100 Subject: [PATCH 5/5] Fix shellcheck error SC2163 --- call_host.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/call_host.sh b/call_host.sh index c9ae0d8..c98644e 100755 --- a/call_host.sh +++ b/call_host.sh @@ -44,7 +44,8 @@ if is_zsh; then else # bash export_func(){ - export -f "$1" 2>/dev/null || true + [ -n "$1" ] || return + eval "export -f $1" 2>/dev/null || true } declare_assoc(){ # create named associative array in bash