[Gentoo] Enhancing a bash backtrace

Last night, ferringb mentioned to me that he wanted to pull a bash backtrace function from eselect into portage. So I took a look at it, and it seemed like a great idea. But it was missing one thing that could be really helpful: It didn’t show the arguments to functions.

Here’s a sample of the original output:

You’ll notice that not only does it lack arguments, but it says bleb() is in the wrong file, and it tries to attribute the original failure to main(). This doesn’t make a lot of sense, so we change both the sourcefile array, $(basename ${BASH_SOURCE[${n}]}), and the function name array, funcname=${FUNCNAME[${n} – 1]}, by 1. Here’s the enhanced output:

Here’s the chunk that does most of the heavy lifting:

This code is part of a loop printing the most recent function first. We initialize p to 0 at the beginning of looping through the call stack, then increment it by the size of the argc to that function on each pass through the loop: ${BASH_ARGC[$n – 1]. This is necessary because bash only has one-dimensional arrays. Thus, BASH_ARGC[] contains the argc values for every function in the call stack, but BASH_ARGV[] contains a concatenated list of every single argument without clear separators between functions.

If we wanted the most recent function last, we’d have to change things around a little:

In particular, note how j starts at 0 instead of the last argc, and how newarg is set in a very different way. We need to treat p differently when going in reverse order (it’s initialized to ${#BASH_ARGV[@]}, the size of the argv array), which necessitates the changed treatment of the rest.

We enclose the whole loop in a test for the existence of BASH_ARGV for backwards compatibility with bash-2: BASH_ARGV and BASH_ARGC weren’t incorporated into bash until 3.0. Another caveat is that neither of the arrays are set without the extdebug setting near the top of your code:

Thanks to ferringb for way too much discussion about how the output format should look and for making us put the effort into figuring out how to switch the ordering around for compatible output with Python tracebacks from portage.

For completeness, here’s the beginning of the loop in both cases, respectively:

The complete code is available for both versions.