245

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'
}
codeforester
  • 39,467
  • 16
  • 112
  • 140
terminus
  • 13,745
  • 8
  • 34
  • 37
  • Thanks. I used this to conditionally define stubbed out versions of functions when loading a shell library. `fn_exists foo || foo() { :; }` – Harvey Jul 15 '13 at 10:02
  • 3
    You can save the grep by using `type -t` and `==`. – Roland Weber Apr 20 '18 at 08:41
  • 1
    Does not work when locale is non-english. `type test_function` says `test_function on funktio.` when using Finnish locale and `ist eine Funktion` when using German. – Kimmo Lehto Sep 06 '18 at 06:55
  • 4
    For non-english locales `LC_ALL=C` to the resque – gaRex Feb 16 '19 at 03:55

15 Answers15

260

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
Stabledog
  • 3,110
  • 2
  • 32
  • 43
JBB
  • 4,543
  • 3
  • 24
  • 25
  • 143
    `type -t $function` is the meal ticket. – Allan Wind Sep 17 '08 at 18:06
  • 5
    Why didn't you post it as an answer? :-) – terminus Sep 19 '08 at 20:45
  • Because I had posted my answer using declare first :-) – Allan Wind Sep 20 '08 at 18:47
  • 6
    `type [-t]` is nice to *tell* you what a thing is, but when *testing* if something is a function, it's slow since you have to pipe to grep or use backticks, both of which spawn a subprocess. – Lloeki Dec 19 '13 at 08:46
  • 1
    Unless I misread, using *type* will have to perform an admittedly minimal access, to check if there is a matching file. @Lloeki, you're quite correct, but it is the option that produces minimal output, and you can still use the errorlevel. You could get the result without a subprocess, eg `type -t realpath > /dev/shm/tmpfile ; read < /dev/shm/tmpfile` (bad example). However, **declare** is the best answer since is has 0 disk io. – Orwellophile Dec 19 '13 at 23:38
  • @AllanWind You definitely are allowed to write multiple answers, though I suppose back in 2008 this may not have been the case. – Steven Lu Feb 22 '15 at 13:36
  • @Lloeki the return value check still works fine, so I could do `type -t foo && echo "foo is something"`, I see no reason to use `declare` instead, as it still produces output that if we need to not show we have to pipe to /dev/null... – Steven Lu Feb 22 '15 at 13:39
  • @StevenLu what if you need to know that foo is a function specifically? Isn't that only possible with declare (without using subprocess or Orwellophile's example above)? – David Winiecki Apr 15 '15 at 23:09
  • I don't know what I am but I know what I am not, and that's a shell expert.... that being said, it sounds like `declare` is your best friend if you can be satisfied with just a yes/no value as to whether your symbol is a function or not a function, but I was mainly comparing both the syntax and output of `type` and `declare` and it was really obvious that `type -t` is a lot more self-documenting for general purpose shell scripting. You are shell scripting. Subshell to make the script easier to understand? Hell yes. – Steven Lu Apr 16 '15 at 02:51
  • The other side of what I was saying was that you can STILL get away without a subshell/pipe/etc if you are satisfied to simply know if your symbol is defined or not (and being a function does satisfy that condition). – Steven Lu Apr 16 '15 at 02:55
  • Also see https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html#index-type. – Big McLargeHuge Apr 26 '19 at 15:57
  • for zsh this becomes `[[ $(type -w foo) == "foo: function" ]] && echo "Foo exists"` – rolly Oct 12 '22 at 14:21
110

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 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
Alan W. Smith
  • 24,647
  • 4
  • 70
  • 96
Allan Wind
  • 23,068
  • 5
  • 28
  • 38
  • 1
    worked awesome for me. Especially as my shell doesn't have the -t flag for type (I was having a lot of trouble with type "$command" ) – Dennis Apr 06 '12 at 22:00
  • 3
    Indeed, it also works in zsh (useful for rc scripts), and doesn't require to grep for the type. – Lloeki Dec 19 '13 at 08:39
  • 2
    @DennisHodapp no need for `type -t`, you can rely on the exit status instead. I have long used `type program_name > /dev/null 2>&1 && program_name arguments || echo "error"` to see whether I would be able to call something. Obviously the `type -t` and the above method also allows to detect the type, not just whether it's "callable". – 0xC0000022L Feb 11 '15 at 20:08
  • @0xC0000022L what if program_name is not a function? – David Winiecki Apr 15 '15 at 22:42
  • @DavidWiniecki: not sure what you're referring to?!? I was responding to another comment. If it's not a function it could still be callable and the syntax would be the same, whether alias, function, built-in or external command. – 0xC0000022L Apr 16 '15 at 18:11
  • 3
    @0xC0000022L I was nitpicking about how using the exit status doesn't let you know if program_name is a function, but now I think you did address that when you said "Obviously the type -t and the above method also allows to detect the type, not just whether it's "callable"." Sorry. – David Winiecki Apr 16 '15 at 19:06
  • To get 1 if function exists and 0 if not exists: `declare -f $1 > /dev/null && echo 1 || echo 0;` – VladSavitsky May 15 '20 at 07:35
  • What the heck? Any description? Usually, such answers on `StackOverflow` are just humbled with `-1`s. – Artfaith Mar 06 '21 at 05:54
  • I added a description as requested. Bring on the upvotes :-) – Allan Wind Mar 06 '21 at 07:21
47

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
jpaugh
  • 6,634
  • 4
  • 38
  • 90
Orwellophile
  • 13,235
  • 3
  • 69
  • 45
  • 3
    The '-f' option is redundant. – Rajish Aug 22 '13 at 10:03
  • 3
    The `-F` option des not exist in zsh (useful for portability) – Lloeki Dec 19 '13 at 13:35
  • `-F` also is not necessary really: it seems to suppress the function definition/body only. – blueyed Sep 21 '16 at 14:03
  • 3
    @blueyed It may not be necessary, but it is highly desirable, we are trying to confirm a function exists, not list it's entire contents (which is somewhat inefficient). Would you check if a file is present using `cat "$fn" | wc -c`? As for zsh, if the *bash* tag didn't clue you in, maybe the question itself should have. "Determine if a function exists in bash". I would further point out, that while the `-F` option does not exist in zsh, it also does not cause an error, therefore the use of both -f and -F allows the check to succeed in both *zsh* and *bash* which otherwise it wouldn't. – Orwellophile Sep 21 '16 at 15:24
  • @Orwellophile `-F` is used in zsh for floating point numbers. I cannot see why using `-F` makes it better in bash?! I got the impression that `declare -f` works the same in bash (regarding the return code). – blueyed Sep 22 '16 at 21:45
  • @blueyed: in **bash** `-F` skips the function definition, and `-f` is superfluous but harmless. The correct answer was actually `declare -f "$fn"`. For reasons unknown, I used both flags. It was pure serendipity to later find that in **zsh** the `-F` flag is meaningless but harmless. Given `declare -fF "$fn"` works in both shells, and doesn't require me double checking the arguments, it's an instant winner for me. And if you can't see why -F is preferable to -f, then you probably consider `cat "$filename" || fail` to be equiv. to `test -f "$filename" || fail`. Same principle. – Orwellophile Sep 23 '16 at 07:43
  • @Orwellophile I got the impression that `-F` would be harmful in zsh, but apparently it is not. – blueyed Sep 23 '16 at 21:45
  • `return $?` is redundant, remove it and the last exit status will still be returned. – FelipeC Dec 29 '20 at 15:54
21

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.

Grégory Joseph
  • 1,549
  • 16
  • 14
  • Nice, my fav from the group! Don't you want double quotes around the argument too? As in `[ $(type -t "$1")"" == 'function' ]` – quickshiftin Nov 26 '13 at 17:10
  • Thanks @quickshiftin; I don't know if I want those double quotes, but you're probably right, although .. can a function even be declared with a name that'd need to be quoted ? – Grégory Joseph Dec 06 '13 at 11:43
  • 6
    You're using bash, use `[[...]]` instead of `[...]` and get rid of the quote hack. Also backticks fork, which is slow. Use `declare -f $1 > /dev/null` instead. – Lloeki Dec 19 '13 at 08:43
  • 4
    Avoiding errors with empty arguments, reducing quotes, and using the '=' posix compliant equality, it can be safely reduced to:: `fn_exists() { [ x$(type -t $1) = xfunction ]; }` – qneill Aug 10 '15 at 13:27
12

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 !

  • 1
    It can be done without grep: `test_type_nogrep () { a () { echo 'a' ;}; local b=$(type a); c=${b//is a function/}; [ $? = 0 ] && return 1 || return 0; }` – qneill Aug 10 '15 at 13:21
  • @qneill I made somewhat more extensive test in [my answer](http://stackoverflow.com/a/40693218/4414935), – jarno Nov 19 '16 at 13:04
  • 1
    PIPE is the most slowest element. This test does not compare `type` and `declare`. It compares `type | grep` with `declare`. This is a big difference. – kyb May 24 '18 at 11:22
8

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.

jarno
  • 787
  • 10
  • 21
  • Attention here: `declare -F f` doesn't return non-zero value if f doesn't exists on zsh, but bash yes. Be careful using it. `declare -f f`, by another hand, works as expected attaching the definition of the function on the stdout (which can be annoying...) – Manoel Vilela Oct 30 '17 at 09:43
  • 1
    Have you tried `test_type3 () { [[ $(type -t f) = function ]] ; }` there is a marginal cost of defining a local var (although < 10%) – Oliver Feb 03 '19 at 14:22
7

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.

Scott
  • 71
  • 1
  • 1
  • 3
    For maximum succinctness use `isFunction() { declare -F "$1"; } >&-` – Neil May 28 '12 at 23:01
  • 3
    `isFunction() { declare -F -- "$@" >/dev/null; }` is my recommendation. It works on a list of names as well (succeeds only if all are functions), gives no problems with names starting with `-` and, at my side (`bash` 4.2.25), `declare` always fails when output is closed with `>&-`, because it cannot write the name to stdout in that case – Tino May 13 '16 at 11:37
  • And please be aware, that `echo` can sometimes fail with "interrupted system call" on some platforms. In that case "check && echo yes || echo no" still can output `no` if `check` is true. – Tino May 13 '16 at 11:42
4

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
qneill
  • 1,643
  • 14
  • 18
4

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; }
it3xl
  • 2,372
  • 27
  • 37
3

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

fn_exists()
{
  type $1 >/dev/null 2>&1;
}
Jason Plank
  • 2,336
  • 5
  • 31
  • 40
3
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
$
Yunus
  • 181
  • 6
3

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
}
Community
  • 1
  • 1
b1r3k
  • 802
  • 8
  • 15
2

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
2

You can check them in 4 ways

fn_exists() { type -t $1 >/dev/null && echo 'exists'; }
fn_exists() { declare -F $1 >/dev/null && echo 'exists'; }
fn_exists() { typeset -F $1 >/dev/null && echo 'exists'; }
fn_exists() { compgen -A function $1 >/dev/null && echo 'exists'; }
k_vishwanath
  • 1,326
  • 2
  • 20
  • 28
1

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!

Noah Spurrier
  • 508
  • 5
  • 8
  • test_type_nogrep () { a () { echo 'a' ;}; local b=$(type a); c=${b//is a function/}; [ $? = 0 ] && return 1 || return 0; } – qneill – Alexx Roche Jun 23 '17 at 04:33