Reputation: 13471
I have a function which expect to receive another function to be executed
a(){
function=$1
echo "common log! $function"
$function --> run the function
}
What I want is to pass that function argument in my function as a nested function
b(){
a f(){ echo "nested function b" }
echo "since I´m doing more things here"
}
c(){
a f(){ echo "nested function c" }
echo "since I´m doing more things here"
}
But seems like the nested function f cannot be done on bash
Any suggestion about how to accomplish this?
Upvotes: 5
Views: 6590
Reputation: 29148
As other answerers have stated, Bash only supports global functions. However, that doesn't need to deter you. All you have to do is define your "inner" function as a global one anyway, but just give it a name no one else will use. This is a technique called "defunctionalization." Do note that you have to be really careful to avoid executing malicious code, or it might start playing your script like a violin.
__inner_b_key=0 # one counter per script, or function, or inner function, or whatever
b() {
local f
f=__inner_b_f_$((inner_b_key++))
# Yes, it's evil
# But, it's powerful
eval "function ${f}() { echo \"nested function b\"; }"
a $f
}
You can compose this with another trick, "lambda lifting". If your inner functions are any sort of complicated, the quoting mandated by eval
will quickly drive you nuts. Lambda lifting is the process of lifting inner functions into global functions, turning their free variables into parameters. Since Bash already lifts "local" functions to the global scope, you get a really nice effect:
__inner_c_key=0
c() {
local f
local computed
computed=$(doSomething)
# Do something with computed
__inner_c_f() {
# local variables are received as the first arguments
local computed
computed=$1
shift
# the arguments passed to the wrapper appear after the closure arguments
# Do another thing with computed
}
# a closure is created, by storing the values of c's local variables into
# the global definition of an anonymous function that wraps the real implementation
# said anonymous wrapper also forwards the arguments it receives after the arguments
# consumed to pass the closure
# ${var@Q} is a Bash 4.4 feature that quotes the value $var such that Bash can
# reinterpret it back to the same value, which is perfect for eval
f=__inner_c_f_$((inner_c_key++))
eval "function ${f}() { __inner_c_f ${computed@Q} \"\$@\"; }"
higherOrder $f
# a function with a closure was passed to a higher order function! in Bash!
# even after this call to c ends, the value of computed lives on inside
# the definition of $f, which is global and acts as the closure
# too bad we lack garbage collection!
}
These transformations are exceedingly mechanical, to the point that it seems like you acting as compiler, translating a hypothetical "functional Bash" into normal Bash by utilizing the same techniques as other compilers turning functional code into imperative code. As you are following this mechanical process, you can hopefully make sure, easily, that your eval
s are sane and not evil.
Upvotes: 3
Reputation: 44344
You can have nested functions by using a subshell function - use parentheses instead of braces:
#!/bin/bash
a() {
echo "hello a"
}
# Note the (
b() (
echo "hello b"
a() {
echo "inner a"
}
a
)
a
b
a
Gives:
hello a
hello b
inner a
hello a
What you can't do is pass that inner function elsewhere, because the inner function only exists in the subshell. Bash doesn't have references to functions, only global names.
If you want to code something like a closure then use a language like Python.
Upvotes: 10
Reputation: 23830
Just define and pass by name:
b(){
f(){ echo "nested function"; }
a f
echo "since I´m doing more things here"
}
Note however, that nested functions don't seem to be a thing in bash.
So after running the above code, f
will be available in the global scope.
The above code is thus equivalent to:
f(){
echo "nested function"
}
b(){
a f
echo "since I´m doing more things here"
}
Upvotes: 5