Determine if a function exists in bash
Asked Answered
C

15

253

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'
}
Capercaillie answered 17/9, 2008 at 17:57 Comment(4)
Thanks. I used this to conditionally define stubbed out versions of functions when loading a shell library. fn_exists foo || foo() { :; }Nuclei
You can save the grep by using type -t and ==.Durden
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.Elector
For non-english locales LC_ALL=C to the resqueJadda
B
269

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
Bacchae answered 17/9, 2008 at 18:0 Comment(12)
type -t $function is the meal ticket.Sasaki
Why didn't you post it as an answer? :-)Capercaillie
Because I had posted my answer using declare first :-)Sasaki
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.Litigate
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.Karalee
@AllanWind You definitely are allowed to write multiple answers, though I suppose back in 2008 this may not have been the case.Airliner
@Litigate 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...Airliner
@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)?Visayan
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.Airliner
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).Airliner
Also see gnu.org/software/bash/manual/html_node/….Skill
for zsh this becomes [[ $(type -w foo) == "foo: function" ]] && echo "Foo exists"Truckage
S
119

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
Sasaki answered 17/9, 2008 at 18:4 Comment(9)
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" )Oliana
Indeed, it also works in zsh (useful for rc scripts), and doesn't require to grep for the type.Litigate
@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".Snivel
@Snivel what if program_name is not a function?Visayan
@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.Snivel
@Snivel 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.Visayan
To get 1 if function exists and 0 if not exists: declare -f $1 > /dev/null && echo 1 || echo 0;Pustule
What the heck? Any description? Usually, such answers on StackOverflow are just humbled with -1s.Suppletory
I added a description as requested. Bring on the upvotes :-)Sasaki
K
50

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
Karalee answered 2/3, 2012 at 8:8 Comment(8)
The '-f' option is redundant.Kinnikinnick
The -F option des not exist in zsh (useful for portability)Litigate
-F also is not necessary really: it seems to suppress the function definition/body only.Overman
@Overman 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.Karalee
@Karalee -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).Overman
@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.Karalee
@Karalee I got the impression that -F would be harmful in zsh, but apparently it is not.Overman
return $? is redundant, remove it and the last exit status will still be returned.Polynices
W
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.

Webbing answered 25/1, 2012 at 11:34 Comment(4)
Nice, my fav from the group! Don't you want double quotes around the argument too? As in [ $(type -t "$1")"" == 'function' ]Abey
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 ?Uruguay
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.Litigate
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 ]; }Robespierre
N
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 :

declare

real    0m0.064s
user    0m0.040s
sys     0m0.020s
type
    
real    0m2.769s
user    0m1.620s
sys     0m1.130s

declare is a helluvalot faster !

Nonet answered 12/2, 2010 at 15:57 Comment(3)
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; }Robespierre
@Robespierre I made somewhat more extensive test in my answer,Publicity
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.Affliction
P
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.

Publicity answered 19/11, 2016 at 13:3 Comment(2)
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...)Nat
Have you tried test_type3 () { [[ $(type -t f) = function ]] ; } there is a marginal cost of defining a local var (although < 10%)Graziano
A
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.

Allotment answered 28/5, 2012 at 18:9 Comment(3)
For maximum succinctness use isFunction() { declare -F "$1"; } >&-Ruscio
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 caseAffricative
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.Affricative
R
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
Robespierre answered 11/10, 2017 at 14:34 Comment(0)
C
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; }
Corella answered 9/2, 2021 at 0:1 Comment(0)
L
3

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

fn_exists()
{
  type $1 >/dev/null 2>&1;
}
Lexicologist answered 8/10, 2009 at 22:46 Comment(0)
A
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
}
Adulthood answered 27/2, 2012 at 14:29 Comment(1)
his original solution hould work if he puts type call inside the quotes.Sift
H
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
$
Hiltner answered 31/1, 2016 at 17:50 Comment(0)
I
3

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'; }
Inefficient answered 6/10, 2022 at 6:17 Comment(1)
What are the differences between all these?Saffren
M
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
Mendelssohn answered 8/10, 2009 at 22:18 Comment(0)
S
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!

Shan answered 25/6, 2010 at 21:9 Comment(1)
test_type_nogrep () { a () { echo 'a' ;}; local b=$(type a); c=${b//is a function/}; [ $? = 0 ] && return 1 || return 0; } – qneillAscension

© 2022 - 2024 — McMap. All rights reserved.