26

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?

codeforester
  • 39,467
  • 16
  • 112
  • 140
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • Does this answer your question? [How do I list the functions defined in my shell?](https://stackoverflow.com/questions/4471364/how-do-i-list-the-functions-defined-in-my-shell) – amphetamachine Mar 16 '21 at 19:27
  • @amphetamachine, hmm. Good question whether to close this as a duplicate of the other, or the inverse -- this one was first of the two to be asked, and has more answers (10 vs 8). Do you have a reason to prefer this direction, of the two possible approaches? – Charles Duffy Mar 16 '21 at 19:41

10 Answers10

43

How about compgen:

compgen -A function   # compgen is a shell builtin
Alan W. Smith
  • 24,647
  • 4
  • 70
  • 96
trevvor
  • 446
  • 4
  • 2
  • 7
    Perfect! Simple, clean, and even allows prefix matching (as in: `compgen -A function prefix_`) – Charles Duffy Apr 13 '10 at 13:59
  • ...except, an important caveat: this only works with copies of bash having interactive extensions enabled. That excludes the default copy of bash on NixOS, f/e. – Charles Duffy Sep 29 '22 at 18:39
11
$ 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
Kevin Little
  • 12,436
  • 5
  • 39
  • 47
6

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

Jed Daniels
  • 24,376
  • 5
  • 24
  • 24
3

Use the declare builtin to list currently defined functions:

declare -F
Juliano
  • 39,173
  • 13
  • 67
  • 73
  • Helpful (though several other folks already suggested it), but not sufficient in and of itself; the output still needs to be processed into an array to be similar to `${!prefix_*}`, and adding `cut` to the command line isn't as clean as sticking to builtins. – Charles Duffy Apr 13 '10 at 14:01
3

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
Martin von Wittich
  • 350
  • 1
  • 5
  • 19
2

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)

  • Good call -- I didn't think about reading the `declare -f`s into the array and stripping them back out at expansion time. Might make it `declare -F && printf '\0'` inside the process substitution in the 2.0.4 case -- that way the `read` will have an exit status of zero (and, as a bonus, any error that somehow stopped `declare -F` from working would be effectively passed through). – Charles Duffy Nov 09 '16 at 21:41
  • @CharlesDuffy Thanks, added. –  Nov 09 '16 at 21:56
1

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.

FutureNerd
  • 769
  • 7
  • 9
  • Can you explain what it is doing? – Bernhard Feb 06 '14 at 16:11
  • That looks rather like it's built to answer a different question -- using `which` plays no role in listing defined functions at all. – Charles Duffy Mar 04 '14 at 16:57
  • @Bernhard, hopefully it's a little clearer now. @CharlesDuffy, Yes. I am not using the existing `which` to list defined functions, I'm adding a defined function list _to_ `which`. I've edited the post to say why. – FutureNerd Mar 21 '14 at 03:57
  • @FutureNerd, if the goal is to answer "what happens when I run this command?", then how does this differ from what `type` does out-of-the-box? (Also, `type` handles things such as builtins, shell syntax, and the like that this does not). – Charles Duffy Aug 15 '14 at 14:32
  • @Charles, thanks for the clue! How this differs is that this works if I forget "type" and type "which". Maybe I'll define "which" as an alias for "type", heh. – FutureNerd Aug 28 '14 at 19:32
1
#!/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"
Dean Rather
  • 31,756
  • 15
  • 66
  • 72
  • Why the echo? `<(compgen -A function)` would be more efficient. Might also put the work of the grep into the awk line: `awk '/>/ {print $2}'`, removing the redundency there. Actually -- better to ditch `diff` altogether; it's **vastly** less efficient than `comm` when all you want to do is set comparisons. – Charles Duffy Aug 29 '14 at 14:43
  • (Beyond that -- nifty! The question this answers isn't the one that I asked, but it's still moderately useful to have around in case someone else stumbles upon this question looking for something different). – Charles Duffy Aug 29 '14 at 14:46
  • Thanks Charles! I've learned so much about Bash over the last week. Workin' on a boilerplate type thing at the moment to try and learn cool stuff. https://gist.github.com/deanrather/5719199 – Dean Rather Aug 29 '14 at 15:31
  • One more quibble: Expansions should be inside quotes. `echo "$foo"` is better than `echo $foo`, inasmuch as the latter expands globs, string-splits on runs of whitespace (and lets the `echo` command reassemble them -- thereby changing newlines and tabs to spaces), and otherwise does undesirable things. – Charles Duffy Aug 29 '14 at 15:49
  • 1
    ...compare `foo='*'; echo $foo` to `foo='*'; echo "$foo"`. – Charles Duffy Aug 29 '14 at 15:51
  • Actually, this only works the first time you source a file. If you were to source it again, the `original_function_list` would contain all the functions from the previous time. What is I need is some way to finalise that list, make it read-only.. – Dean Rather Aug 29 '14 at 16:24
  • 1
    You don't need to make it read-only, you only need to check whether it's set. `[[ $original_function_list ]] || original_function_list=$(...)` – Charles Duffy Aug 29 '14 at 16:29
1

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.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
1

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
Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • Very nice, though the IFS twiddling on both ends makes things a little ugly. OTOH, the variant that comes off the top of my head to avoid it involve a subshell and string splitting (ie. `funcs=( $(IFS=$'\n'; funcs=($(declare -F)); echo "${funcs[@]##* }") )`), and which is certainly much more functional (as opposed to aesthetic) ugliness. – Charles Duffy Apr 13 '10 at 01:28