Call a Dynamically Created Function Name in a Shell Script
One use case could be to refactor a bunch of if conditions in 1 function into separate isolation functions.
Prefer video? Here it is on YouTube.
I recently had a use case in a command line tool where I wanted to do something specific depending on what type of input a user provided.
I wanted to add a few environment variables into a file but the variables were different depending on what input the user provided. For example, imagine if you had 8 different apps each with their own environment variables.
We can do this in a POSIX shell compliant way.
Without dynamic function calls you might choose to do this:
#!/usr/bin/env sh
set -o errexit
set -o nounset
APP="${1}"
create_env() {
contents=
if [ "${APP}" = "foo" ]; then
contents=$(cat << EOF
FOO=something
ANOTHER_VAR=cool
EOF
)
elif [ "${APP}" = "bar" ]; then
contents=$(cat << EOF
BAR=something_else
NICE=thanks
EOF
)
elif [ "${APP}" = "baz" ]; then
contents=$(cat << EOF
BAZ=yep
HELLO=world
EOF
)
fi
[ -n "${contents}" ] && echo "${contents}" > .env
}
create_env
Then if you ran it, you’d get this output:
$ ./demo foo
$ cat .env
FOO=something
ANOTHER_VAR=cool
There are of course many ways to solve this specific problem. For example, you
could create different env files on disk and cat out the specific app’s .env
file with less code.
I simplified this example a little for the sake of this post. In the real use case, some of these values were being dynamically created based on other variables in the script so I wanted to handle defining the env vars in the script itself.
With that said, here’s how you can refactor the above script to split out the logic of each condition into its own function and then dynamically call a specific function depending on which app name you pass in:
#!/usr/bin/env sh
set -o errexit
set -o nounset
APP="${1}"
create_foo_env() {
cat << EOF > .env
FOO=something
ANOTHER_VAR=cool
EOF
}
create_bar_env() {
cat << EOF > .env
BAR=something_else
NICE=thanks
EOF
}
create_baz_env() {
cat << EOF > .env
BAZ=yep
HELLO=world
EOF
}
"create_${APP}_env"
Using the script is the same as the first version we covered. We don’t even
need to protect against an empty .env
file from being generated because set -o nounset
will throw an error if we input an app name that doesn’t have a
function associated with it.
The takeaway is the last line. It’s really that easy. You can reference the variable as part of the function name. I wrapped it in quotes to make shellcheck happy but it technically works without quotes too.
I find the above mentally easier to reason about because the contents of the file isn’t mixed in with the conditions to figure out which content to use. This example only has 3 apps with 2 env vars too. It was much worse with 8 apps which had more env vars.
I used a similar pattern when creating dynamic variables in a previous post.
Sometimes it’s nice to apply the same concept to solve multiple use cases.
Also, if you’re curious I covered using heredocs
(the << EOF
syntax) in detail in the past.
The video below covers both versions of the code.
# Demo Video
Timestamps
- 0:30 – Without dynamic function calls
- 2:24 – Refactoring the script to use a dynamic function call
- 4:48 – IMO this makes the code easier to read
When was the last time you dynamically called a function? Let us know!