Charles Duffy
Charles Duffy

Reputation: 295272

Listing defined functions in Bash

I'm trying to write some code in bash which uses introspection to select the appropriate function to call.

Determining the candidates requires knowing which functions are defined. It's easy to list defined variables in bash using only parameter expansion:

$ prefix_foo="one"
$ prefix_bar="two"
$ echo "${!prefix_*}"
prefix_bar prefix_foo

However, doing this for functions appears to require filtering the output of set -- a much more haphazard approach.

Is there a Right Way?

Upvotes: 27

Views: 9272

Answers (11)

Leon S.
Leon S.

Reputation: 3677

Since some other questions that want to do the same but in a POSIX shell environment have been closed as duplicates without a good answer, I thought I might add a POSIX compatible variation here.

NOTICE: Posix is not all-encompassing with it's rules and implementations can still work differently (like set and grep -o, see comments). This answer is the best I could do to get a compatible way to do what the question-asker asked, but for a specific platform you might have to play around with the commands.

If you run the set command without any arguments, it will list all available functions in any POSIX environment including their implementation. The output is in a format that is consistent regardless of how you defined it, so you can reliably grep out the function name parts like this:

set | grep '^[a-zA-Z_][a-zA-Z0-9_]\+[[:space:]]\+()'

This will return a list like this:

func1 () 
func2 () 
func3 () 

Explanation: You basically grep searches for a string (only including valid characters) followed by a space followed by ().

If you only want the names you can do it like this:

set | grep '^[a-zA-Z_][a-zA-Z0-9_]\+[[:space:]]\+()' | sed "s/[[:space:]]\+.*$//"

This uses sed to strip everything after the name (grep -o could be used, but the flag is not necessarily supported, see the first comment).

Notice we can not rely on using grep only, because even though the function name is usually the only part at the start of the line, it isn't always (for example with HERE strings or stuff like that make it weird).

Upvotes: 0

Martin von Wittich
Martin von Wittich

Reputation: 393

zsh only (not what was asked for, but all the more generic questions have been closed as a duplicate of this):

typeset -f +

From man zshbuiltins:

-f     The  names  refer  to functions rather than parameters.
 +     If `+' appears by itself in a separate word as the last
       option, then the names of all parameters (functions with -f)
       are printed, but  the values  (function  bodies)  are not.

Example:

martin@martin ~ % cat test.zsh 
#!/bin/zsh

foobar()
{
  echo foobar
}

barfoo()
{
  echo barfoo
}

typeset -f +

Output:

martin@martin ~ % ./test.zsh
barfoo
foobar

Upvotes: 3

user8017719
user8017719

Reputation:

This has no issues with IFS nor globbing:

readarray -t funcs < <(declare -F)

printf '%s\n' "${funcs[@]##* }"

Of course, that needs bash 4.0.

For bash since 2.04 use (a little trickier but equivalent):

IFS=$'\n' read -d '' -a funcs < <(declare -F)

If you need that the exit code of this option is zero, use this:

IFS=$'\n' read -d '' -a funcs < <( declare -F && printf '\0' )

It will exit unsuccesful (not 0) if either declare or read fail. (Thanks to @CharlesDuffy)

Upvotes: 4

trevvor
trevvor

Reputation: 476

How about compgen:

compgen -A function   # compgen is a shell builtin

Upvotes: 46

Dean Rather
Dean Rather

Reputation: 32374

#!/bin/bash
# list-defined-functions.sh
# Lists functions defined in this script.
# 
# Using `compgen -A function`,
# We can save the list of functions defined before running out script,
# the compare that to a new list at the end,
# resulting in the list of newly added functions.
# 
# Usage:
#   bash list-defined-functions.sh      # Run in new shell with no predefined functions
#   list-defined-functions.sh           # Run in current shell with plenty of predefined functions
#

# Example predefined function
foo() { echo 'y'; }

# Retain original function list
# If this script is run a second time, keep the list from last time
[[ $original_function_list ]] || original_function_list=$(compgen -A function)

# Create some new functions...
myfunc() { echo "myfunc is the best func"; }
function another_func() { echo "another_func is better"; }
function superfunction { echo "hey another way to define functions"; }
# ...

# function goo() { echo ok; }

[[ $new_function_list ]] || new_function_list=$(comm -13 \
    <(echo $original_function_list) \
    <(compgen -A function))

echo "Original functions were:"
echo "$original_function_list"
echo 
echo "New Functions defined in this script:"
echo "$new_function_list"

Upvotes: 1

FutureNerd
FutureNerd

Reputation: 799

This collects a list of function names matching any of a list of patterns:

functions=$(for c in $patterns; do compgen -A function | grep "^$c\$")

The grep limits the output to only exact matches for the patterns.

Check out the bash command type as a better alternative to the following. Thanks to Charles Duffy for the clue.

The following uses that to answer the title question for humans rather than shell scripts: it adds a list of function names matching the given patterns, to the regular which list of shell scripts, to answer, "What code runs when I type a command?"

which() {
  for c in "$@"; do
    compgen -A function |grep "^$c\$" | while read line; do
      echo "shell function $line" 1>&2
     done
    /usr/bin/which "$c"
   done
 }

So,

(xkcd)Sandy$ which deactivate
shell function deactivate
(xkcd)Sandy$ which ls
/bin/ls
(xkcd)Sandy$ which .\*run_hook
shell function virtualenvwrapper_run_hook

This is arguably a violation of the Unix "do one thing" philosophy, but I've more than once been desperate because which wasn't finding a command that some package was supposed to contain, me forgetting about shell functions, so I've put this in my .profile.

Upvotes: 2

Dennis Williamson
Dennis Williamson

Reputation: 359845

Pure Bash:

saveIFS="$IFS"
IFS=$'\n'
funcs=($(declare -F))      # create an array
IFS="$saveIFS"
funcs=(${funcs[@]##* })    # keep only what's after the last space

Then, run at the Bash prompt as an example displaying bash-completion functions:

$ for i in ${funcs[@]}; do echo "$i"; done
__ack_filedir
__gvfs_multiple_uris
_a2dismod
. . .
$ echo ${funcs[42]}
_command

Upvotes: 1

Kevin Little
Kevin Little

Reputation: 12976

$ declare -F
declare -f ::
declare -f _get_longopts
declare -f _longopts_func
declare -f _onexit
...

So, Jed Daniel's alias,

declare -F | cut -d" " -f3

cuts on a space and echos the 3rd field:

$ declare -F | cut -d" " -f3
::
_get_longopts
_longopts_func
_onexit

Upvotes: 14

Jed Daniels
Jed Daniels

Reputation: 25526

I have an entry in my .bashrc that says:

alias list='declare -F |cut -d" " -f3'

Which allows me to type list and get a list of functions. When I added it, I probably understood what was happening, but I can't remember to save my life at the moment.

Good luck,

--jed

Upvotes: 7

Juliano
Juliano

Reputation: 41367

Use the declare builtin to list currently defined functions:

declare -F

Upvotes: 3

Charles Duffy
Charles Duffy

Reputation: 295272

One (ugly) approach is to grep through the output of set:

set \
  | egrep '^[^[:space:]]+ [(][)][[:space:]]*$' \
  | sed -r -e 's/ [(][)][[:space:]]*$//'

Better approaches would be welcome.

Upvotes: 1

Related Questions