Bash,查找内容与搜索字符串匹配的函数

Bash,查找内容与搜索字符串匹配的函数

我想在 bash 中搜索所有定义的函数以查找某些搜索字符串。下面是一个开始,但我想删除所有的术语不是下一行后跟空格(即消除$1在该函数主体中未找到的所有条目)。

fu() { declare -f | grep -e \(\) -e $1; }

例如这个输出:

...
tt ()
untargz ()
urlfix ()
ver ()
    [ -f /etc/lsb-release ] && RELEASE="$(cat /etc/lsb-release | grep DESCRIPTION | sed 's/^.*=//g' | sed 's/\"//g') ";
vi. ()
vi.su ()
...

会减少到

...
ver ()
    [ -f /etc/lsb-release ] && RELEASE="$(cat /etc/lsb-release | grep DESCRIPTION | sed 's/^.*=//g' | sed 's/\"//g') ";
...

一个更好的方法(如果可能的话)是如果每个匹配函数都可以被确定并完整显示。

我设想大致如下:

  • 收集函数的名称以及其主体中的搜索字符串(函数的名称始终是匹配项之前的一行中的单个单词,从 at 开始,^后跟一个空格,然后以 结尾的行),然后对每个函数()$使用command -V名称,或者,declare -f再次执行 a,但这一次,使用这些名称并匹配它们之后的所有内容,从{}(其中{}各自在单行上^- 我知道 grep/awk/sed 可以对那些拥有这些知识的人做出惊人的事情。

最终结果将运行,它将向我显示函数体中fu awk包含的每个函数的定义。awk

答案1

awk我想到了管道接收端的以下命令:

declare -f | awk -v srch="pattern" 'NF==2 && $2=="()"{if (m) print buf; buf=""; m=0}
                                    buf && index($0,srch){m=1}
                                    {buf=buf ORS $0}
                                    END{if (m) print buf}'

buf这个想法是在解析 的输出时将每个函数的声明和主体存储在缓冲区中declare -f,但仅在找到搜索字符串时才打印缓冲区。

  • ()如果程序遇到仅包含两个字段(= 空格分隔的“单词”)的行,其中第二个字段是,则程序将识别新函数定义的开始。如果在解析前一个函数时找到匹配项(由标志为 1 表示),则将打印m缓冲区。buf缓冲区和标志都将被重置。
  • awk搜索词作为变量传递给程序srch。如果在当前行找到它(index函数返回非零结果),m则标志设置为 1,但前提是我们不在函数声明开始的行上(通过buf不为空来指示),否则匹配在函数中姓名也算。
  • 每行都会被追加到 buffer 中buf,并通过输出记录分隔符ORS(默认为换行符)与之前的内容分隔开。
  • 最后,如果找到匹配,则执行另一次检查,如果找到,则打印缓冲区。如果没有该检查,将永远不会考虑最后一个函数定义。

笔记

index()该程序使用的函数执行全字符串搜索awk。如果您希望搜索基于正则表达式匹配,则需要将条件更改为

index($0,srch)

$0~srch

(但是,一如既往,请注意,搜索包含对正则表达式具有特殊含义的字符的字符串会变得更加麻烦)。

答案2

对于那些希望在 中做同样事情的人zsh,要获取其主体与模式匹配的函数的函数定义,您可以这样做:

typeset -f '<dummy>' ${(k)functions[(R)pattern]}

用于<dummy>覆盖没有匹配功能的情况。或者更干净的方式:

() { (($#)) && typeset -f -- "$@"; } ${(k)functions[(R)pattern]}

在 中bash,您可以执行类似的操作:

compgen -A function | (
  ret=1
  while IFS= read -r fn; do
    def=$(typeset -f -- "$fn") &&
      [[ ${def#*'()'} = pattern ]] &&
      printf '%s\n' "$def" &&
      ret=0
  done
  exit "$ret"
)

尽管这是相当低效的,因为函数名称列表一次读取一个字节(因为 的read输入是一个管道),并且每个函数都会分叉一个进程。它可以通过以下方式进行优化:

compgen -A function | (
  readarray -t functions
  for fn in "${functions[@]}"; do
    typeset -f -- "$fn" && printf '\0'
  done
) | (
  ret=1
  readarray -td '' fn_definitions
  for def in "${fn_definitions[@]}"; do
    [[ ${def#*'()'} = pattern ]] &&
      printf '%s\n' "$def" &&
      ret=0
  done
  exit "$ret"
)

readarray与 相反read,不需要一次读取一个字节的输入,因为无论如何它都会消耗所有输入。我们用 NUL 字符分隔函数定义,因为无论如何bash(与 相反zsh)不能将该字符存储在其函数定义/名称或变量内容中,然后我们可以使用readarray -td ''(需要 bash-4.4+)将其分解为数组。

这避免了必须依赖启发式方法,例如 的存在()(这也很可能出现在函数体内)来猜测每个函数定义在 的输出中开始的位置typeset -f

相关内容