237

If I have a Bash script like:

#!/bin/bash

f() {
  # echo function name, "f" in this case
}

Is there any way to do this? This could be used in help messages such as

printf "Usage: %s: blah blah blah \n" $(basename $0) >&2; 

Only in this case what I wanted is not $0, which is the file name of the script.

codeforester
  • 39,467
  • 16
  • 112
  • 140
Yan
  • 3,249
  • 4
  • 20
  • 12

6 Answers6

335

You can use ${FUNCNAME[0]} in bash to get the current function name.

(More info on FUNCNAME in GNU bash reference manual).

Vlastimil Burián
  • 3,024
  • 2
  • 31
  • 52
TheBonsai
  • 15,513
  • 4
  • 22
  • 14
106

From the Bash Reference Manual:

FUNCNAME

An array variable containing the names of all shell functions currently in the execution call stack. The element with index 0 is the name of any currently-executing shell function. The bottom-most element (the one with the highest index) is "main". This variable exists only when a shell function is executing. Assignments to FUNCNAME have no effect and return an error status. If FUNCNAME is unset, it loses its special properties, even if it is subsequently reset.

This variable can be used with BASH_LINENO and BASH_SOURCE. Each element of FUNCNAME has corresponding elements in BASH_LINENO and BASH_SOURCE to describe the call stack. For instance, ${FUNCNAME[$i]} was called from the file ${BASH_SOURCE[$i+1]} at line number ${BASH_LINENO[$i]}. The caller builtin displays the current call stack using this information.

When bash arrays are accessed without an index the first element of the array will be returned, so $FUNCNAME will work in simple cases to provide the name of the immediately current function, but it also contains all other functions in the call stack. For example:

# in a file "foobar"
function foo {
    echo foo
    echo "In function $FUNCNAME: FUNCNAME=${FUNCNAME[*]}" >&2
}

function foobar {
    echo "$(foo)bar"
    echo "In function $FUNCNAME: FUNCNAME=${FUNCNAME[*]}" >&2
}

foobar

Will output:

$ bash foobar
In function foo: FUNCNAME=foo foobar main
foobar
In function foobar: FUNCNAME=foobar main
Community
  • 1
  • 1
bschlueter
  • 3,817
  • 1
  • 30
  • 48
  • 6
    I still don't get it. Why add the `[0]` if it's implied by accessing the undecorated variable? – Tom Hale Aug 09 '16 at 12:44
  • 23
    Because it's deceptive and ignorant of the variable's actual type? Sure, it's not always necessary, but like many other bash-isms, it's a lazy convention. Better to be explicit than ambiguous. – bschlueter Aug 09 '16 at 20:31
38

I use ${FUNCNAME[0]} to print current function name

Verdigrass
  • 1,203
  • 12
  • 14
  • @bschlueter but when you reference an array without treating it like an array in Bash, it just prints the first value; therefore, how is the accepted answer incorrect? – Alexej Magura Nov 01 '16 at 16:40
  • 5
    Sure, and that's convenient, but terrible for maintenance and testing. Don't be lazy, be explicit. – bschlueter Nov 01 '16 at 19:22
18

The simplest way to get the function name (from inside a function) is dependent on which shell you are using:

Zsh version
someFunctionName() {
   echo $funcstack[1]
}
Bash version
someFunctionName() {
   echo ${FUNCNAME[0]}
}
Both
someFunctionName() {
  currentShell=$(ps -p $$ | awk "NR==2" | awk '{ print $4 }' | tr -d '-')
  if [[ $currentShell == 'bash' ]]; then
    echo ${FUNCNAME[0]}
  elif [[ $currentShell == 'zsh' ]]; then
    echo $funcstack[1]
  fi
}
A more robust version
columnX () {
    awk "{print \$$1}"
}
rowX () {
    awk "NR==$1"
}

checkShell() {
    ps -p $$ | columnX 4 | rowX 2 | tr -d - 
    # produces bash or zsh (or other shell name like fish)
}

showMethodName(){
    checkShell && echo ${FUNCNAME[0]} || echo $funcstack[1] 
}
showMethodName
bschlueter
  • 3,817
  • 1
  • 30
  • 48
jasonleonhard
  • 12,047
  • 89
  • 66
  • 3
    Please stop abusing `###` in your answers. – Marcin Orlowski Jun 28 '20 at 17:23
  • 1
    I can confirm that this works with `zsh`. I was getting null results using `FUNCNAME` – Life5ign Sep 08 '20 at 18:27
  • 2
    Perhaps `###` should not exist on the platform then. Why provide something that is expected not to be used in any circumstance? – jasonleonhard Aug 06 '21 at 20:53
  • 2
    A four process pipe with questionable use of awk in a row to determine bash vs zsh? Oh my. `test -n "$ZSH_VERSION" && echo $funcstack[1]; test -n "$BASH_VERSION" && echo ${FUNCNAME[0]}"`. Save the planet. Save four processes today :-) – Jens Mar 20 '22 at 09:11
  • Please stop referring to `###` as abuse. Abuse? Really? Do you need a hug? Smile and have a great day ;D – jasonleonhard Mar 21 '22 at 20:09
2

Another example:

# in a file "foobar"
foo() {
    echo "$FUNCNAME fuction begins"
}

foobar() {
    echo "$FUNCNAME fuction begins"
}

echo 'begin main'
foo
foobar
echo 'end main'

Will output:

begin main
foo fuction begins
foobar fuction begins
end main
0

Without checking which shell is being used, this works with zsh, bash, and bash emulating sh. (However, this still breaks with sh and dash, and possibly others. Feel free to edit and improve.)

funcName() {
    if [ -n "${FUNCNAME[0]}" ]; then
        local function_name="${FUNCNAME[0]}";  # bash, bash emulating sh
    else
        local function_name="${funcstack[1]}";  # zsh
    fi

    echo "$function_name"  # Outputs: funcName
}