Determine if a function exists in bash

BashFunctionTestingScripting

Bash Problem Overview


Currently I'm doing some unit tests which are executed from bash. Unit tests are initialized, executed and cleaned up in a bash script. This script usualy contains an init(), execute() and cleanup() functions. But they are not mandatory. I'd like to test if they are or are not defined.

I did this previously by greping and seding the source, but it seemed wrong. Is there a more elegant way to do this?

Edit: The following sniplet works like a charm:

fn_exists()
{
    LC_ALL=C type $1 | grep -q 'shell function'
}

Bash Solutions


Solution 1 - Bash

Like this: [[ $(type -t foo) == function ]] && echo "Foo exists"

The built-in type command will tell you whether something is a function, built-in function, external command, or just not defined.

Additional examples:

$ LC_ALL=C type foo
bash: type: foo: not found

$ LC_ALL=C type ls
ls is aliased to `ls --color=auto'

$ which type

$ LC_ALL=C type type
type is a shell builtin

$ LC_ALL=C type -t rvm
function

$ if [ -n "$(LC_ALL=C type -t rvm)" ] && [ "$(LC_ALL=C type -t rvm)" = function ]; then echo rvm is a function; else echo rvm is NOT a function; fi
rvm is a function

Solution 2 - Bash

The builtin bash command declare has an option -F that displays all defined function names. If given name arguments, it will display which of those functions are exist, and if all do it will set status accordingly:

$ fn_exists() { declare -F "$1" > /dev/null; }

$ unset f
$ fn_exists f && echo yes || echo no
no

$ f() { return; }
$ fn_exist f && echo yes || echo no
yes

Solution 3 - Bash

If declare is 10x faster than test, this would seem the obvious answer.

Edit: Below, the -f option is superfluous with BASH, feel free to leave it out. Personally, I have trouble remembering which option does which, so I just use both. -f shows functions, and -F shows function names.

#!/bin/sh

function_exists() {
    declare -f -F $1 > /dev/null
    return $?
}

function_exists function_name && echo Exists || echo No such function

The "-F" option to declare causes it to only return the name of the found function, rather than the entire contents.

There shouldn't be any measurable performance penalty for using /dev/null, and if it worries you that much:

fname=`declare -f -F $1`
[ -n "$fname" ]    && echo Declare -f says $fname exists || echo Declare -f says $1 does not exist

Or combine the two, for your own pointless enjoyment. They both work.

fname=`declare -f -F $1`
errorlevel=$?
(( ! errorlevel )) && echo Errorlevel says $1 exists     || echo Errorlevel says $1 does not exist
[ -n "$fname" ]    && echo Declare -f says $fname exists || echo Declare -f says $1 does not exist

Solution 4 - Bash

Borrowing from other solutions and comments, I came up with this:

fn_exists() {
  # appended double quote is an ugly trick to make sure we do get a string -- if $1 is not a known command, type does not output anything
  [ `type -t $1`"" == 'function' ]
}

Used as ...

if ! fn_exists $FN; then
    echo "Hey, $FN does not exist ! Duh."
    exit 2
fi

It checks if the given argument is a function, and avoids redirections and other grepping.

Solution 5 - Bash

Dredging up an old post ... but I recently had use of this and tested both alternatives described with :

test_declare () {
    a () { echo 'a' ;}

    declare -f a > /dev/null
}

test_type () {
    a () { echo 'a' ;}
    type a | grep -q 'is a function'
}

echo 'declare'
time for i in $(seq 1 1000); do test_declare; done
echo 'type'
time for i in $(seq 1 100); do test_type; done

this generated :

real    0m0.064s
user    0m0.040s
sys     0m0.020s
type

real    0m2.769s
user    0m1.620s
sys     0m1.130s

declare is a helluvalot faster !

Solution 6 - Bash

It boils down to using 'declare' to either check the output or exit code.

Output style:

isFunction() { [[ "$(declare -Ff "$1")" ]]; }

Usage:

isFunction some_name && echo yes || echo no

However, if memory serves, redirecting to null is faster than output substitution (speaking of, the awful and out-dated `cmd` method should be banished and $(cmd) used instead.) And since declare returns true/false if found/not found, and functions return the exit code of the last command in the function so an explicit return is usually not necessary, and since checking the error code is faster than checking a string value (even a null string):

Exit status style:

isFunction() { declare -Ff "$1" >/dev/null; }

That's probably about as succinct and benign as you can get.

Solution 7 - Bash

Testing different solutions:

#!/bin/bash

test_declare () {
    declare -f f > /dev/null
}

test_declare2 () {
    declare -F f > /dev/null
}

test_type () {
    type -t f | grep -q 'function'
}

test_type2 () {
	 [[ $(type -t f) = function ]]
}

funcs=(test_declare test_declare2 test_type test_type2)

test () {
	for i in $(seq 1 1000); do $1; done
}

f () {
echo 'This is a test function.'
echo 'This has more than one command.'
return 0
}
post='(f is function)'

for j in 1 2 3; do

	for func in ${funcs[@]}; do
		echo $func $post
		time test $func
		echo exit code $?; echo
	done

	case $j in
	1)  unset -f f
		post='(f unset)'
		;;
	2)  f='string'
		post='(f is string)'
		;;
	esac
done

outputs e.g.:

> test_declare (f is function) > > real 0m0,055s user 0m0,041s sys 0m0,004s exit code 0 > > test_declare2 (f is function) > > real 0m0,042s user 0m0,022s sys 0m0,017s exit code 0 > > test_type (f is function) > > real 0m2,200s user 0m1,619s sys 0m1,008s exit code 0 > > test_type2 (f is function) > > real 0m0,746s user 0m0,534s sys 0m0,237s exit code 0 > > test_declare (f unset) > > real 0m0,040s user 0m0,029s sys 0m0,010s exit code 1 > > test_declare2 (f unset) > > real 0m0,038s user 0m0,038s sys 0m0,000s exit code 1 > > test_type (f unset) > > real 0m2,438s user 0m1,678s sys 0m1,045s exit code 1 > > test_type2 (f unset) > > real 0m0,805s user 0m0,541s sys 0m0,274s exit code 1 > > test_declare (f is string) > > real 0m0,043s user 0m0,034s sys 0m0,007s exit code 1 > > test_declare2 (f is string) > > real 0m0,039s user 0m0,035s sys 0m0,003s exit code 1 > > test_type (f is string) > > real 0m2,394s user 0m1,679s sys 0m1,035s exit code 1 > > test_type2 (f is string) > > real 0m0,851s user 0m0,554s sys 0m0,294s exit code 1

So declare -F f seems to be the best solution.

Solution 8 - Bash

From my comment on another answer (which I keep missing when I come back to this page)

$ fn_exists() { test x$(type -t $1) = xfunction; }
$ fn_exists func1 && echo yes || echo no
no
$ func1() { echo hi from func1; }
$ func1
hi from func1
$ fn_exists func1 && echo yes || echo no
yes

Solution 9 - Bash

Invocation of a function if defined.

Known function name. Let's say the name is my_function, then use

[[ "$(type -t my_function)" == 'function' ]] && my_function;
# or
[[ "$(declare -fF my_function)" ]] && my_function;

Function's name is stored in a variable. If we declare func=my_function, then we can use

[[ "$(type -t $func)" == 'function' ]] && $func;
# or
[[ "$(declare -fF $func)" ]] && $func;

The same results with || instead of &&
(Such a logic inversion could be useful during coding)

[[ "$(type -t my_function)" != 'function' ]] || my_function;
[[ ! "$(declare -fF my_function)" ]] || my_function;

func=my_function
[[ "$(type -t $func)" != 'function' ]] || $func;
[[ ! "$(declare -fF $func)" ]] || $func;

Strict mode and precondition checks
We have set -e as a strict mode.
We use || return in our function in a precondition.
This forces our shell process to be terminated.

# Set a strict mode for script execution. The essence here is "-e"
set -euf +x -o pipefail

function run_if_exists(){
    my_function=$1

    [[ "$(type -t $my_function)" == 'function' ]] || return;

    $my_function
}

run_if_exists  non_existing_function
echo "you will never reach this code"

The above is an equivalent of

set -e
function run_if_exists(){
    return 1;
}
run_if_exists

which kills your process.
Use || { true; return; } instead of || return; in preconditions to fix this.

    [[ "$(type -t my_function)" == 'function' ]] || { true; return; }

Solution 10 - Bash

This tells you if it exists, but not that it's a function

fn_exists()
{
  type $1 >/dev/null 2>&1;
}

Solution 11 - Bash

fn_exists()
{
   [[ $(type -t $1) == function ]] && return 0
}

update

isFunc () 
{ 
    [[ $(type -t $1) == function ]]
}

$ isFunc isFunc
$ echo $?
0
$ isFunc dfgjhgljhk
$ echo $?
1
$ isFunc psgrep && echo yay
yay
$

Solution 12 - Bash

I would improve it to:

fn_exists()
{
    type $1 2>/dev/null | grep -q 'is a function'
}

And use it like this:

fn_exists test_function
if [ $? -eq 0 ]; then
    echo 'Function exists!'
else
    echo 'Function does not exist...'
fi

Solution 13 - Bash

I particularly liked solution from Grégory Joseph

But I've modified it a little bit to overcome "double quote ugly trick":

function is_executable()
{
    typeset TYPE_RESULT="`type -t $1`"
    
    if [ "$TYPE_RESULT" == 'function' ]; then
        return 0
    else
        return 1
    fi
}

Solution 14 - Bash

It is possible to use 'type' without any external commands, but you have to call it twice, so it still ends up about twice as slow as the 'declare' version:

test_function () {
        ! type -f $1 >/dev/null 2>&1 && type -t $1 >/dev/null 2>&1
}

Plus this doesn't work in POSIX sh, so it's totally worthless except as trivia!

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionterminusView Question on Stackoverflow
Solution 1 - BashJBBView Answer on Stackoverflow
Solution 2 - BashAllan WindView Answer on Stackoverflow
Solution 3 - BashOrwellophileView Answer on Stackoverflow
Solution 4 - BashGrégory JosephView Answer on Stackoverflow
Solution 5 - BashjonathanserafiniView Answer on Stackoverflow
Solution 6 - BashScottView Answer on Stackoverflow
Solution 7 - BashjarnoView Answer on Stackoverflow
Solution 8 - BashqneillView Answer on Stackoverflow
Solution 9 - Bashit3xlView Answer on Stackoverflow
Solution 10 - Bashuser186804View Answer on Stackoverflow
Solution 11 - BashYunusView Answer on Stackoverflow
Solution 12 - Bashuser186791View Answer on Stackoverflow
Solution 13 - Bashb1r3kView Answer on Stackoverflow
Solution 14 - BashNoah SpurrierView Answer on Stackoverflow