列出 Bash 中已定义的函数

26

我正在尝试使用内省来选择要调用的适当函数。

确定候选函数需要知道哪些函数已定义。在bash中,只使用参数扩展就可以轻松列出已定义的变量

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

然而,对于函数来说,这似乎需要过滤set的输出——这是一种更加杂乱无章的方法。

有没有正确的方法?


这个回答解决了你的问题吗?如何列出我shell中定义的函数? - amphetamachine
@amphetamachine,嗯。这是一个好问题,是否将其关闭为另一个的重复,还是反过来--这个问题是两个中先被提出的,并且有更多的答案(10个对8个)。您是否有理由更喜欢这种方法,这是两种可能的方法之一? - Charles Duffy
10个回答

44

那么关于 compgen 呢:

compgen -A function   # compgen is a shell builtin

7
完美!简洁清晰,甚至支持前缀匹配(例如:compgen -A function prefix_ - Charles Duffy
除此之外,有一个重要的警告:这仅适用于启用交互扩展的 bash 副本。这排除了 NixOS 上默认的 bash 副本,例如。 - Charles Duffy

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

所以,Jed Daniel的别名,

declare -F | cut -d" " -f3

根据空格分割并输出第三个字段:

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

6

我在我的.bashrc文件中有一个条目,内容如下:

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

这使我可以输入list并获得一个函数列表。当我添加它时,我可能知道发生了什么,但是现在我无论如何都记不起来了。

祝你好运,

--jed


声明 -F | 切割 -d" " -f3 | egrep -v "^_" - expelledboy

3
使用declare内置函数列出当前定义的函数:
declare -F

虽然其他人已经提出了这个建议,但它仍然很有帮助;但是仅凭它本身还不足够,输出仍需要被处理成一个数组,以便类似于${!prefix_*},而在命令行中添加cut并不像坚持使用内置命令那样干净。 - Charles Duffy

3

zsh仅支持(不是要求的内容,但所有更通用的问题都被视为此问题的重复):

typeset -f +

来自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.

示例:

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

foobar()
{
  echo foobar
}

barfoo()
{
  echo barfoo
}

typeset -f +

输出:

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

2

这个没有与IFS或globbing相关的问题:

readarray -t funcs < <(declare -F)

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

当然,这需要bash 4.0以上版本。
对于bash 2.04及以上版本,可使用以下方法(略微复杂但等效):
IFS=$'\n' read -d '' -a funcs < <(declare -F)

如果您需要该选项的退出代码为零,请使用以下命令:
IFS=$'\n' read -d '' -a funcs < <( declare -F && printf '\0' )

如果declareread失败,它将以非0的退出方式结束。感谢@CharlesDuffy


不错的想法——我没有考虑到将declare -f读入数组中,并在扩展时将它们剥离出来。在2.0.4版本的情况下,可能会使其变为declare -F && printf '\0' — 这样,read将具有零的退出状态(并且,作为额外的奖励,任何阻止declare -F工作的错误都会有效地传递)。 - Charles Duffy
@CharlesDuffy 谢谢,已添加。 - user8017719

1

一种(丑陋的)方法是通过set的输出进行grep:

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

欢迎提出更好的方法。


1
在bash中,“丑陋”位于滑动比例尺的左侧。 - Kevin Little

1

纯Bash:

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

然后,在Bash提示符下运行,例如显示bash-completion函数:

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

非常好,尽管两端的IFS调整使事情有点丑陋。另一方面,我脑海中想到的变体涉及子shell和字符串拆分(即funcs =($(IFS = $'\ n'; funcs =($(declare -F)); echo“$ {funcs [@] ## *}”))),这显然更加功能强大(而不是美观)丑陋。 - Charles Duffy

1
这个函数会收集与列表中任何模式匹配的函数名称列表:
functions=$(for c in $patterns; do compgen -A function | grep "^$c\$")

grep限制输出仅匹配模式的精确结果。

查看bash命令type作为以下更好的替代方案。感谢Charles Duffy提供线索。

以下使用它来回答标题问题,而不是shell脚本:它添加了与给定模式匹配的函数名称列表到shell脚本的常规which列表中,以回答“当我键入一个命令时,哪些代码运行?”

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
 }

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

这可能违反了Unix的“只做一件事”的哲学,但我曾经因为which无法找到某个包应该包含的命令而感到绝望,而忘记了shell函数,所以我把它放在了我的.profile文件中。

你能解释一下它在做什么吗? - Bernhard
这似乎更像是为回答不同的问题而构建的 - 使用“which”根本不起到列出已定义函数的任何作用。 - Charles Duffy
@Bernhard,希望现在更清楚了一点。 @CharlesDuffy,是的。我没有使用现有的which来列出已定义的函数,而是将已定义的函数列表添加到which中。我已经编辑了帖子来说明原因。 - FutureNerd
@FutureNerd,如果目标是回答“当我运行此命令时会发生什么”,那么这与type的默认操作有何不同?(此外,type处理内置函数、shell语法等内容,而此命令则不包括在内)。 - Charles Duffy
@Charles,感谢你的提示!这个不同之处在于,如果我忘记“type”并键入“which”,它也能工作。也许我会把“which”定义为“type”的别名,呵呵。 - FutureNerd

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"

为什么要使用echo?<(compgen -A function)会更有效率。也可以将grep的工作放到awk行中:awk '/>/ {print $2}',从而消除冗余。实际上,最好完全放弃diff;当你只想进行设置比较时,它比comm大大不如。 - Charles Duffy
除此之外,这很棒!这个回答的问题并不是我所问的,但如果有人偶然发现这个问题寻找不同的东西,它仍然是相当有用的。 - Charles Duffy
谢谢Charles!上周我学到了很多关于Bash的知识。目前正在开发一个样板类型的东西,以尝试学习一些酷炫的东西。https://gist.github.com/deanrather/5719199 - Dean Rather
1
foo='*'; echo $foofoo='*'; echo "$foo"进行比较。 - Charles Duffy
1
你不需要将其设置为只读,只需要检查它是否已设置。[[ $original_function_list ]] || original_function_list=$(...) - Charles Duffy
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接