Understanding Bash Variable Evaluation in Functions

By default, Bash and POSIX-compliant shells use late binding (or lazy evaluation) for variables inside functions. This means variables inside a function body are not evaluated when the function is declared; instead, they are looked up at the exact moment the function is executed.

Consider the standard behavior below:

myvar="ok"
function myfunc {
    echo "$myvar"
}

myvar="ko"
myfunc # Outputs: ko

If you want a function to "remember" or freeze the value of a variable at the time of definition, you need early binding (immediate variable substitution).

The Solution: Dynamic Function Definition with eval

The most straightforward and standard way to achieve immediate variable evaluation during function declaration is by using the eval built-in command. By wrapping the function definition in double quotes inside eval, shell expansion happens before the function is registered.

myvar="ok"
eval "myfunc() { echo '$myvar'; }"

myvar="ko"
myfunc # Outputs: ok

Handling Runtime Arguments Correctly

A common pitfall with the eval approach occurs when your function also needs to accept runtime positional arguments (like $1 or $@). If you do not escape the positional parameters, eval will evaluate them immediately during function creation (usually expanding them to empty strings).

To prevent this, escape runtime variables with a backslash (\$1):

prefix="[INFO]"

# Note the backslash before $1
eval "log_msg() { echo \"$prefix\" \"\$1\"; }"

prefix="[ERROR]"

log_msg "Server started" # Outputs: [INFO] Server started

Is This POSIX Compliant?

Yes. The eval command and standard function definition syntax (name() { ... }) are fully defined in the POSIX standard. This solution will work identically across various shells including bash, dash, ash, ksh, and zsh.

Summary & Best Practices

  • Default Behavior: Shell functions evaluate variables lazily at execution time.
  • Immediate Binding: Use eval "func() { ... }" to bind variable values at definition time.
  • Escaping: Always escape internal shell variables (like \$1, \$?) that should be evaluated when the function runs, not when it is defined.