Search code examples
bashintrospectionbuilt-in

bash caller builtin stops working from exported function, why?


I have very strange issues using bash and exported function to give me a reliable answer to the call of the builtin caller.

Here's my setup to illustrate this issue: Bash script bar defines and exports function bar1 and bar2. bar2 calls bar1. Bash script bar then execute bash script foo which will call bar1.

The caller builtin will then break only after the call of bar1. Is this normal ? can you explain why ? can you simplify the following code that exposes the issue ?

To be perfectly clear, here's how to reproduce on your system: Build both file:

cd /tmp
cat <<"EOF" > foo 
#!/bin/bash

bar1
EOF
chmod +x foo

cat <<"EOF" > bar 
#!/bin/bash

bar2() {
    echo "$FUNCNAME IN: $(caller 0)" >&2 
} 
export -f bar2

bar1() {
    echo "$FUNCNAME BEFORE: $(caller 0)" >&2
    bar2
    echo "$FUNCNAME AFTER: $(caller 0)" >&2 
}
export -f bar1

./foo
EOF
chmod +x bar

You can then fiddle and see:

$ ./bar
bar1 BEFORE: 3 main ./foo
bar2 IN: 
bar1 AFTER: 

I expected (with acceptable variations on the line numbers):

$ ./bar
bar1 BEFORE: 9 main ./foo
bar2 IN: 5 bar ./foo
bar1 AFTER: 9 main ./foo

Ultimately, my question would be: how could I circumvent this issue and get the caller in all cases ?

ADDITIONAL INFO:

  • bash version: 4.3.42(1)-release (x86_64-pc-linux-gnu) from ubuntu package 4.3-14ubuntu1.

Solution

  • This is a bug in bash. It was fixed in version 4.4.

    In the presence of exported functions, the BASH_SOURCE variable is not properly maintained. You can check it by displaying the contents of the FUNCNAME, BASH_SOURCE, BASH_LINENO special variables:

    cd /tmp
    cat <<"EOF" > foo 
    #!/bin/bash
    
    bar1 
    EOF
    chmod +x foo
    
    cat <<"EOF" > bar 
    #!/bin/bash
    
    bar2() {
        echo "$FUNCNAME IN: $(caller 0) [${FUNCNAME[@]}] [${BASH_SOURCE[@]}] [${BASH_LINENO[@]}]" >&2 
    } 
    export -f bar2
    
    bar1() {
        echo "$FUNCNAME BEFORE: $(caller 0) [${FUNCNAME[@]}] [${BASH_SOURCE[@]}] [${BASH_LINENO[@]}]" >&2
        bar2
        echo "$FUNCNAME AFTER: $(caller 0) [${FUNCNAME[@]}] [${BASH_SOURCE[@]}] [${BASH_LINENO[@]}]" >&2 
    } 
    export -f bar1
    
    ./foo
    EOF
    chmod +x bar
    

    Output of ./bar:

    bar1 BEFORE: 3 main ./foo [bar1 main] [./foo] [3 0]
    bar2 IN:  [bar2 bar1 main] [./foo] [1 3 0]
    bar1 AFTER:  [bar1 main] [] [3 0]
    

    As you can see, stack frames corresponding to invocations of exported functions aren't added to BASH_SOURCE, but whenever a function returns the topmost stack frame is popped.

    Note that the FUNCNAME variable is not affected by this bug. Thus, if you need only the name of the caller you can obtain it as ${FUNCNAME[1]}.