I am writing a large shell program comprised of several source files. I was looking for a basic import system that accomplishes three goals.
- Avoid sourcing entire files for access to just a few functions
- Prevent files from being sourced multiple times.
- Imported functions must be executed in the context of their script so that internal dependancies do not have to be imported individually.
Please see the comments on the last function called import
for the usage.
Any feedback on whether or not this is a relatively efficient implementation would be appreciated.
core.sh:
#!/bin/sh
# Core utilities
# Author: arctelix
# Make sure repo and library are available
if ! [ "$IMPORT_DIR" ]; then
read -p "Enter directory to import from: " IMPORT_DIR < /dev/tty
fi
debug_text="\e[0;90m"
rc="\e[0m" #reset color
# Prints debug messages when DEBUG=true
debug () {
if [ "$DEBUG" = true ]; then
printf "%b%b%b\n" $debug_text "$1" $rc 1>&2
fi
}
# a print function that does not interfere with function output
print () {
printf "%b%b%b\n" $debug_text "$1" $rc 1>&2
}
# Test for the existence of a command
cmd_exists() {
if ! [ "$1" ];then return 1;fi
command -v $1 >/dev/null 2>&1
}
# Test if script contains function
script_func_exists() {
script_exists "$1"
$1 command -v $2 >/dev/null 2>&1
}
# Test if script exists
script_exists() {
if [ -f "$1" ]; then
chmod u+x "$1"
cmd_exists "$1"
return $?
fi
return 1
}
# Creates a local function to call an external script's functions without
# polluting the local scope with all of the external function names.
# Also allows for sourcing external scripts without redundancy.
# external script (only script name available locally):
# import platforms
# PLATFORM="$(platforms get_platform)"
# external script function (only imported function name available locally):
# import platforms get_platform
# PLATFORM="$(get_platform)"
# source external script (makes all functions available locally):
# import source platforms
# PLATFORM="$(get_platform)"
import () {
usage="import [source] <file> [<func>]"
usage_full="
source Source entire file
file File to import
func Function to import
"
if [ ! "$1" ];then
echo "$usage"
echo "$usage_full"
return
fi
local source
if [ "$1" = "source" ]; then
source="true"
shift
fi
local file_name="$1"
local script="$IMPORT_DIR/${file_name}.sh"
local func_name="$2"
# if local func exists nothing to do
if [ "$func_name" ] && cmd_exists "$func_name" ; then
debug "already imported $func_name"
return
elif ! [ "$func_name" ] && cmd_exists "$file_name" ; then
debug "already imported $file_name"
return
fi
# test for script
if ! script_exists "$script" ; then
echo "IMPORT ERROR: $file_name does not exist"
# test for script func
elif [ "$func_name" ] && ! script_func_exists "$script" "$func_name"; then
echo "IMPORT ERROR: $file_name $func_name does not exist"
fi
# source: sources script and creates a placeholder function
# causing a second call to import source to be aborted.
if [ "$source" ]; then
debug "importing source $file_name $func_name"
source "$script"
eval "${file_name}() {
debug \"call sourced $file_name : \$@ \"
\"\$@\"
}"
return
fi
# import: executes script functions in a sub-shell preventing
# inadvertant local name collisions.
if [ "$func_name" ]; then
debug "importing $func_name"
eval "${func_name}() {
debug \"call $func_name : \$@\"
# Source file into subshell and execute function
( source \"$script\"; $func_name \"\$@\" )
}"
else
debug "importing $file_name"
eval "${file_name}() {
debug \"call $file_name : \$@ \"
# Source file into subshell and execute function
( source \"$script\"; \"\$@\" )
}"
fi
}
Example usage of import function:
1) Create a directory import_test
and add core.sh allong with the following two files.
main.sh (this is an example of your program entry point)
export IMPORT_DIR="./"
source ./core.sh
import user get_name
echo "hello $(get_name)"
user.sh (this is an example of a module for use in main.sh)
get_name () {
echo "$(whoami)"
}
2) Make sure the files have execute permissions:
cd test_import
chmod 755 ./*
3) Run the main script
bash ./main.sh
You will see the output: hello yourname
4) You can use the import command in you shell as follows:
source ./core.sh
5) When prompted for an import directory enter ./
.
6) Now you can do the following:
import user get_user
echo $(get_user).
Notes: In the above example there must be a file called ./user.sh
and it must have a function named get_user
.
If you run the import
command without any parameters you will now get the usage message rather then an error.