💾 Archived View for thatit.be › 2023-02-16-09-00-17.gmi captured on 2024-06-16 at 12:48:32. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2024-05-26)

➡️ Next capture (2024-07-09)

🚧 View Differences

-=-=-=-=-=-=-

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:

In your scripts that use it:

Implementation

Imagine you have a function, foo, you use in your script and you want to put it in a library, library.sh. But if someone calls your library directly you want to print a message about how to use it.

Here is what library.sh might look like:

SOURCED=( $(caller) )
sourced(){
    test "${SOURCED[0]}" -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 "${@}"
fi

And a sample script that sources the library and makes use of its function.

. library.sh

foo

And that’s it. Now if library.sh is executed, it will print that message, but if it’s sourced, as in the above script, it just makes the function foo 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:

import() {
    local FUNCTION=${1}
    local LIBRARY=${3}
eval << EOF
. ${LIBRARY} ;
declare -f ${FUNCTION}
EOF
}

Now, a script that we’ll call lol.sh that uses that and the aforementioned library.sh:

. import.sh
import foo from library.sh
foo

Now if the above lol.sh is called as a script, it will be able to call only the function foo from library.sh. It would not, for example, be able to call main or any other functions because import.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 library.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:

FOO_SOURCED=( $(caller) )
foo_sourced(){
    test "${FOO_SOURCED[0]}" -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 "${@}"
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 caller

From the man page:

   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

Navigation

index

tags

prev ⏰

⏰ next

updated: 2023-02-16 09:29:54

generated: 2024-06-15