9

I have a Bourne Shell script that has several functions in it, and allows to be called in the following way:

my.sh <func_name> <param1> <param2>

Inside, func_name() will be called with param1 and param2.

I want to create a help function that would just list all available functions, even without parameters.

The question: how do I get a list of all function names in a script from inside the script?

I'd like to avoid having to parse it and look for function patterns. Too easy to get wrong.

Update: the code. Wanted my help() function be like main() - a function added to the code is added to the help automatically.

#!/bin/sh

# must work with "set -e"

foo ()
{
    echo foo: -$1-$2-$3-
    return 0
}

# only runs if there are parameters
# exits
main ()
{
    local cmd="$1"
    shift
    local rc=0
    $cmd "$@" || rc=$?
    exit $rc
}

if [[ "$*" ]]
then
    main "$@"
    die "how did we get here?"
fi
Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
n-alexander
  • 14,663
  • 12
  • 42
  • 43
  • 1
    "Bourne shell"? I don't know of **any** Linux distro that ships Bourne out-of-the-box, or ever has; `/bin/sh` is pretty much universally POSIX sh (a decades-newer standard with a nonzero set of incompatibilities; for instance, Bourne treats `^` as a pipe character, POSIX sh does not; Bourne uses `$[ ]` for math, POSIX sh uses `$(( ))`, etc). – Charles Duffy May 11 '16 at 21:05
  • @CharlesDuffy Do you have a source for that? – Ole Tange Jun 28 '16 at 09:34
  • @OleTange, which part of it? (I was certainly relying on the "on Linux" to leave out cases such as SunOS / older Solaris where POSIX sh has been in a different location, but (1) that was referenced in my comment, and (2) the question is so tagged). – Charles Duffy Jun 28 '16 at 13:00
  • @OleTange, ...if you want to look up Bourne using `$[ ]` for math and POSIX sh not incorporating a mandate for compatibility with that syntax into the standard, that's a quick Heirloom Bourne runtime invocation and a look at the POSIX spec. Likewise for `^` being a synonym for `|` -- which, incidentally, is how Autotools distinguishes whether it's on Bourne. – Charles Duffy Jun 28 '16 at 13:10
  • @CharlesDuffy I am looking for a source to confirm that you are correct that Bourne shell does/did indeed use ^ as pipe and that the 1989 version did not use $(( )) for math. – Ole Tange Jun 28 '16 at 16:31
  • http://www.in-ulm.de/~mascheck/bourne/common.html, re: `^` as pipe – Charles Duffy Jun 28 '16 at 16:53
  • 2
    @OleTange, ..and re: `$(( ))`, even **today's** Heirloom Bourne doesn't support `$(( ))` for math. Go get the source yourself from http://heirloom.sourceforge.net/sh.html – Charles Duffy Jun 28 '16 at 16:57
  • 1
    ...that said, I was mistaken about `$[ ]` being a Bourneism. Correct Bourne practice is to use `expr` for math. – Charles Duffy Jun 28 '16 at 16:59

4 Answers4

16

You can get a list of functions in your script by using the grep command on your own script. In order for this approach to work, you will need to structure your functions a certain way so grep can find them. Here is a sample:

$ cat my.sh
#!/bin/sh

function func1() # Short description
{
    echo func1 parameters: $1 $2
}

function func2() # Short description
{
    echo func2 parameters: $1 $2
}

function help() # Show a list of functions
{
    grep "^function" $0
}

if [ "_$1" = "_" ]; then
    help
else
    "$@"
fi

Here is an interactive demo:

$ my.sh 
function func1() # Short description
function func2() # Short description
function help() # Show a list of functions


$ my.sh help
function func1() # Short description
function func2() # Short description
function help() # Show a list of functions


$ my.sh func1 a b
func1 parameters: a b

$ my.sh func2 x y
func2 parameters: x y

If you have "private" function that you don't want to show up in the help, then omit the "function" part:

my_private_function()
{
    # Do something
}
Hai Vu
  • 37,849
  • 11
  • 66
  • 93
  • the way of doxygen :) This is good since it allows for a description to be printed, not just function name. But no enforcement, a forgotten comment is a forgotten function. Thanks – n-alexander Apr 16 '10 at 08:38
  • actually, this looks nice and simple. Use of "function" to differentiate between public and private is nice, and it's robust enough for my purposes – n-alexander Apr 16 '10 at 08:49
  • 1
    if you're interested, your solution with eval messes up spaces in parameters, i.e. my.sh func1 "1 1" 2 3 will call func1 with $1=1 $2=1 $3=2 $4=3 instead of $1="1 1", etc. Avoiding eval helps – n-alexander Apr 16 '10 at 08:53
  • n-alexander: Thanks for the tip regarding dropping the eval--it was unnecessary and incorrect. Regarding your first comment: if you forgot a function's comment, it is still showing up in the help. – Hai Vu Apr 16 '10 at 15:08
  • 2
    The question specified "Bourne shell". The `function` keyword is not allowed in Bourne, or even in POSIX sh; it's a ksh and bash extension only. – Charles Duffy May 11 '16 at 21:04
  • Good catch! if replace $0 to $BASH_SOURCE, will more accurately, when you source it instead run it, still can get exact correct result. – zw963 Sep 25 '18 at 15:29
14

typeset -f returns the functions with their bodies, so a simple awk script is used to pluck out the function names

f1 () { :; }
f2 () { :; }
f3 () { :; }
f4 () { :; }
help () {
    echo "functions available:"
    typeset -f | awk '/ \(\) $/ && !/^main / {print $1}'
}
main () { help; }
main

This script outputs:

functions available:
f1
f2
f3
f4
help
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
  • 3
    +1 for a clean solution that _potentially_ works in `bash`, `ksh`, `zsh`: since the output from `typeset -f` differs slightly across shells, the `awk` command needs to be more flexible: `typeset -f | awk '!/^main[ (]/ && /^[^ {}]+ *\(\)/ { gsub(/[()]/, "", $1); print $1}'`. Small caveat in `bash`: If you have _exported_ functions in your environment, `typeset -f` will list them too. – mklement0 May 20 '14 at 18:20
  • @glenn is there a way to make the awk command show the comments also ? – n00b Dec 26 '21 at 14:23
  • Note - there is no `typeset` in `sh`. It will only work if your `sh` is a symlink to `bash`, which it is on Linux, the OP's system. For a system like FreeBSD which has a real `sh`, this won't work. – user9645 May 17 '22 at 10:27
5

You call this function with no arguments and it spits out a "whitespace" separated list of function names only.


function script.functions () {
    local fncs=`declare -F -p | cut -d " " -f 3`; # Get function list
    echo $fncs; # not quoted here to create shell "argument list" of funcs.
}

To load the functions into an array:


declare MyVar=($(script.functions));

Of course, common sense dictates that any functions that haven't been sourced into the current file before this is called will not show up in the list.

To Make the list read-only and available for export to other scripts called by this script:


declare -rx MyVar=($(script.functions));

To print the entire list as newline separated:


printf "%s\n" "${MyVar[@]}";
  • `declare -F` works only (as intended) in `bash`, but not in `ksh` (breaks), and not in `zsh` (where `-F` declares a floating-point variable). – mklement0 May 20 '14 at 18:01
  • I suspect the following would be a bit more portable & yield the same result: `declare -f | grep '^.*\s()' | cut -f 1 -d ' '`. – Dennis Estenson Dec 30 '16 at 15:55
1

The best thing to do is make an array (you are using bash) that contains functions that you want to advertise and have your help function iterate over and print them.

Calling set alone will produce the functions, but in their entirety. You'd still have to parse that looking for things ending in () to get the proverbial symbols.

Its also probably saner to use something like getopt to turn --function-name into function_name with arguments. But, well, sane is relative and you have not posted code :)

Your other option is to create a loadable for bash (a fork of set) that accomplishes this. Honestly, I'd prefer going with parsing before writing a loadable for this task.

Tim Post
  • 33,371
  • 15
  • 110
  • 174
  • @Tim +1 for your suggestion. @n-alexander I think there is no way to get meta-data on functions. You could parse, but you don't want to do that. – ring bearer Apr 13 '10 at 15:55
  • you mean using the same array for calling the functions and for printing them? This is what I'd do in C. In shell I was hoping to be able to just add a function and not have to change any more code. But thanks – n-alexander Apr 16 '10 at 08:36
  • @n-alexander - Barring writing your own bash internal to export only 'symbols' , you're going to have to make two changes per function. I know it seems like you _should_ be able to `just get` the functions since bash knows about them, but there is no included interface to do so. You've actually got me thinking of making a loadable to do this. – Tim Post Apr 16 '10 at 08:57