Inspired by the Php require_once I figured how it could be implemented for modern Bash with associative arrays here:
a.sh
#!/usr/bin/env bash
declare -gAi __REQUIRES
require_once() {
for p; do
r=$(readlink --canonicalize-existing "$p")
# shellcheck disable=SC1090 # Dynamic source
[[ -r "$r" && 1 -ne "${__REQUIRES[$r]}" ]] && __REQUIRES[$r]=1 && . "$r"
done
}
require_once ./b.sh
require_once ./b.sh
hello
b.sh
hello() {
printf 'I am the hello function in b.sh.\n'
}
This works as intended:
- Get the real path of the
source. - Check it is readable, check it is not already loaded by looking-up the global associative array key.
- If so, register it in the global associative array and source it.
Now I am still wondering how to:
- Get the real path of the source in a more portable/standard way not depending on Linux Gnu Tools?
- Have it work with older Bash like 3.2
- Have it work with POSIX-shell grammar without array.
CodePudding user response:
Get the real path of the source in a more portable/standard way not depending on Linux Gnu Tools?
readlink -f is available on Busybox. Do readlink after checking that the path exists.
Anyway, https://www.google.com/search?q=readlink POSIX -> https://medium.com/mkdir-awesome/posix-alternatives-for-readlink-21a4bfe0455c , https://github.com/ko1nksm/readlinkf .
Have it work with older Bash like 3.2
Have it work with POSIX-shell grammar without array.
POSIX does not like newlines in filenames anyway, so just store files as lines:
__REQUIRES=""
if ! printf "%s" "$__REQUIRES" | grep -Fq "$r"; then
__REQUIRES="$__REQUIRES""$r
"
. "$r"
fi
Or maybe use case so that you do not fork:
__REQUIRES="
"
case "$__REQUIRES" in
*"
$r
"*) ;;
*)
__REQUIRES="$__REQUIRES""$r
"
. "$r"
;;
esac
If you want to handle newlines in filenames, convert filename via xxd or od (both are available on Busybox, od is POSIX) and store hex representation of filenames as lines in the variable.
CodePudding user response:
POSIX-shell grammar method using symbolic links in temp directory as already required source markers:
a.sh
#!/usr/bin/env sh
unset __REQUIRES;
trap 'rm -fr -- "$__REQUIRES"' EXIT INT
__REQUIRES=$(mktemp -d)
require_once() {
for p; do
# Path is readable or continue
[ -r "$p" ] || continue
# Get real path
r=$(readlink -f -- "$p")
# New real path in __REQUIRES temp dir
nr="$__REQUIRES$r"
# If path __REQUIRES temp dir exists, file is already sourced
[ -r "$nr" ] && continue
# Make equivalent path for file in __REQUIRES temp dir
mkdir -p -- "${nr%/*}"
# Create symbolic link to __REQUIRES temp dir
ln -sf -- "$r" "$nr"
# shellcheck disable=SC1090 # Dynamic source
. "$r"
done
}
counter=0
require_once ./b.sh
require_once ./b.sh
hello
printf 'counter=%d\n' "$counter"
b.sh
counter=$((counter 1))
hello() {
printf 'I am the hello function in b.sh.\n'
}
