TIL: bash caller
This was actually last week, but I'm making a new category for "Today I Learned..." (TIL) so it's easier for me to find things like this in the future when I want to refer back to a new thing.
I'm documenting this everywhere because it's been a little bit of a point of contention in my scripts for a while. And the solution, as it always has been, already exists in the damn shell. I don't know how I missed it, or why I never saw it on stack overflow, but you can structure a bash script such that it behaves like a python module with regard to being source-able or executable directly and not require any extra work on the part of the caller.
Here's the Recipe
In your modules:
• set a variable to Bash built-in caller at the global scope
• make a convenience function to test that variable for a 0
• If 0, run your main -- it was called as a script
• If any other value, it's an import -- it was called via source
In your scripts that use it:
• just import the module -- source it
Implementation
Imagine you have a function, T383838foo, you use in your script and you want to put it in a library, T383838library.sh. But if someone calls your library directly you want to print a message about how to use it.
Here is what T383838library.sh might look like:
T282828
SOURCED=( $(caller) )
sourced(){
test "Tff7b72${Te6edf3SOURCEDTb4b4b4[T79c0ff0Tb4b4b4]Tff7b72}" -ne 0
}
foo(){
echo "Hello from foo!"
}
main(){
echo "Hi, I'm just a library, source me instead of calling me directly."
}
if ! sourced ; then
main "Tff7b72${Tff7b72@Tff7b72}"
fi
And a sample script that sources the library and makes use of its function.
T282828
. library.sh
foo
And that's it. Now if T383838library.sh is executed, it will print that message, but if it's sourced, as in the above script, it just makes the function T383838foo available.
Now stop reading...
Stop reading if you didn't grasp the above. This next bit of foolishness is for my amusement, but if you're still reading and you don't understand the above, it could make it more confusing.
Let's make it more Pythonic and import just one function from a library instead of all of the functions, first, a library that provides such a magical import method:
T282828
import() {
local FUNCTION=Tff7b72${T79c0ff1Tff7b72}
local LIBRARY=Tff7b72${T79c0ff3Tff7b72}
eval <Tff7b72< EOF
. ${LIBRARY} ;
declare -f ${FUNCTION}
EOF
}
Now, a script that we'll call T383838lol.sh that uses that and the aforementioned T383838library.sh:
T282828
Tff7b72. Te6edf3importTff7b72.Td2a8ffsh
Tff7b72import T7ee787foo Tff7b72from T7ee787libraryT7ee787.T7ee787sh
Te6edf3foo
Now if the above T383838lol.sh is called as a script, it will be able to call only the function T383838foo from T383838library.sh. It would not, for example, be able to call T383838main or any other functions because T383838import.sh used a subshell to source the library and then executed the resulting declaration of that function.
This will fall apart if the function being imported needs to call any other functions in T383838library.sh, but I still thought it was amusing.
improvements
While using this technique for real I found that sourcing multiple libraries that also used this was problematic. I had to adapt it slightly, but I am leaving the above in tact because it's clearer to read and understand.
An easy way to make this still work is to prefix the function and variables with a unique substring derived from the file name or purpose. For example:
T282828
FOO_SOURCED=( $(caller) )
foo_sourced(){
test "Tff7b72${Te6edf3FOO_SOURCEDTb4b4b4[T79c0ff0Tb4b4b4]Tff7b72}" -ne 0
}
foo(){
echo "Hello from foo!"
}
main(){
echo "Hi, I'm just a library, source me instead of calling me directly."
}
if ! foo_sourced ; then
main "Tff7b72${Tff7b72@Tff7b72}"
fi
I'm still experimenting to make something that is automatic and portable that mimics namespaces in other languages. I've tried dynamically creating some new variables using random but it was hard to read and due to array use wasn't the same across different versions of Bash. I am very close though with subscripting an array with the file name that is setting the variable.
Bash Built-in T383838caller
From the man page:
T282828
caller [expr]
Returns the context of any active subroutine call (a shell function or a script executed
with the . or source builtins). Without expr, caller displays the line number and source
filename of the current subroutine call. If a non-negative integer is supplied as expr,
caller displays the line number, subroutine name, and source file corresponding to that
position in the current execution call stack. This extra information may be used, for
example, to print a stack trace. The current frame is frame 0. The return value is 0
unless the shell is not executing a subroutine call or expr does not correspond to a
valid position in the call stack.
Tags: index, til, bash
Tags
Navigation