我如何找到某些bash函数的定义位置?


Answers:


32

打开调试。从Bash手册

extdebug

如果是在shell调用或在shell启动文件中设置的,则与shell --debugger选项相同,安排在shell启动之前执行调试程序配置文件 。如果在调用后设置,则启用了供调试器使用的行为:

  • -F选项将declare内建(见击内置命令)显示对应于作为自变量提供的每个功能名称的源文件名和行号。

例:

$ bash --debugger
$ declare -Ff quote
quote 143 /usr/share/bash-completion/bash_completion

确实:

$ nl -ba /usr/share/bash-completion/bash_completion | sed -n 143,150p
   143  quote()
   144  {
   145      local quoted=${1//\'/\'\\\'\'}
   146      printf "'%s'" "$quoted"
   147  }
   148
   149  # @see _quote_readline_by_ref()
   150  quote_readline()

这是一个很棒且简单的解决方案。对于您的示例,甚至不需要启动新的shell :shopt -s extdebug; declare -Ff quote; shopt -u extdebug
佳诺

2
@jarno啊,好吧,与我的名字相反,我使用zsh。这就是为什么我开始一个新的外壳。:D
bashity mcbashface

是否有类似的方法来查找别名声明位置?
FedonKadifeli

@fedon我下面复杂而复杂的方法应该可以工作。
58,terdon

1
您可以将此功能设为:find_function()( shopt -s extdebug; declare -F "$@"; )。使用函数体上的括号,它会在子shell中执行,并且对shopts的更改不会影响调用方。而且-f似乎不需要。
wjandrea

15

实际上,这比起初看起来要复杂得多。Shell读取哪些文件取决于当前正在运行的Shell类型。无论是交互式还是非交互式,是登录外壳程序还是非登录外壳程序,以及上述各项的组合。要搜索所有可由不同的Shell读取的默认文件,可以执行以下操作(更改$functionName为要查找的函数的实际名称):

grep "$functionName" ~/.bashrc ~/.profile ~/.bash_profile ~/.bash.login \
                     ~/.bash_aliases /etc/bash.bashrc /etc/profile \
                     /etc/profile.d/* /etc/environment 2> /dev/null

如果这样不起作用,则可能是使用.或其别名调用了非默认文件source。要找到这种情况,请运行:

grep -P '(^|\s)(\.|source)\s+' ~/.bashrc ~/.profile ~/.bash_profile \
                               ~/.bash.login ~/.bash_aliases /etc/bash.bashrc \
                               /etc/profile /etc/profile.d/* /etc/environment 2> /dev/null

那可能需要一些解释。在-P使Perl兼容的正则表达式(PCRE),这让我们用一些票友正则表达式的语法。特别:

  • (^|\s):匹配行(^)或空格(\s)的开头。
  • (\.|source)\s+:匹配文字.字符(\.)或单词source,但前提是它们后面跟随一个或多个空格字符。

这就是给我系统的原因:

$ grep -P '(^|\s)(\.|source)\s+' ~/.bashrc ~/.profile ~/.bash_profile \
>                                ~/.bash.login ~/.bash_aliases /etc/bash.bashrc \
>                                /etc/profile /etc/profile.d/* /etc/environment 2> /dev/null
/home/terdon/.bashrc:   . /etc/bashrc
/home/terdon/.bashrc:   . /etc/bash_completion
/home/terdon/.bashrc:. $HOME/scripts/git-prompt.sh
/home/terdon/.bashrc:#  echo -n "$n : "; grep "^CA"  $n |perl -e 'my ($a,$c)=0; while(<>){$c++;next if /cellular_component_unknown/; next if /biological_process/; $a++} print "$a Classes of $c annotated (" . $a*100/$c . ")\n"' 
/etc/bash.bashrc:[ -r /usr/share/bash-completion/bash_completion   ] && . /usr/share/bash-completion/bash_completion
/etc/profile:       test -r "$profile" && . "$profile"
/etc/profile:   . /etc/bash.bashrc
/etc/profile.d/locale.sh:    . "$XDG_CONFIG_HOME/locale.conf"
/etc/profile.d/locale.sh:    . "$HOME/.config/locale.conf"
/etc/profile.d/locale.sh:    . /etc/locale.conf
/etc/profile.d/Z97-byobu.sh:        . /usr/bin/byobu-launch
/etc/profile.d/Z97-byobu.sh:        . /usr/bin/byobu-launch
/etc/profile.d/Z97-byobu.sh:        . /usr/bin/byobu-launch
/etc/profile.d/Z97-byobu.sh:        . /usr/bin/byobu-launch
/etc/profile.d/Z97-byobu.sh:        . /usr/bin/byobu-launch

如您所见,这将打印整个匹配的行。我们真正感兴趣的是调用的文件名列表,而不是调用它们的行。您可以使用更复杂的正则表达式获得这些:

grep -hPo '(^|\s)(\.|source)\s+\K\S+' ~/.bashrc ~/.profile ~/.bash_profile \
                                      ~/.bash.login ~/.bash_aliases \
                                      /etc/bash.bashrc /etc/profile \
                                      /etc/profile.d/* /etc/environment 2> /dev/null

-h标志禁止显示找到匹配项的文件名的打印,grep默认情况下,当被告知搜索多个文件时,该操作会进行打印。的-o意思是“仅打印线的匹配部分”。添加到正则表达式的其他内容是:

  • \K:忽略到目前为止所有匹配的内容。这是PCRE技巧,可让您使用复杂的正则表达式来查找匹配项,但在使用grep -o标志时不包括该匹配部分。

在我的系统上,以上命令将返回:

$ grep -hPo '(^|\s)(\.|source)\s+\K\S+' ~/.bashrc ~/.profile ~/.bash_profile \
>                                       ~/.bash.login ~/.bash_aliases \
>                                       /etc/bash.bashrc /etc/profile \
>                                       /etc/profile.d/* /etc/environment 2> /dev/null
/etc/bashrc
/etc/bash_completion
$HOME/scripts/git-prompt.sh
$a*100/$c
")\n"'
/usr/share/bash-completion/bash_completion
"$profile"
/etc/bash.bashrc
"$XDG_CONFIG_HOME/locale.conf"
"$HOME/.config/locale.conf"
/etc/locale.conf
/usr/bin/byobu-launch
/usr/bin/byobu-launch
/usr/bin/byobu-launch
/usr/bin/byobu-launch
/usr/bin/byobu-launch

请注意,我碰巧使用.了一个空格,该空格用于采购,但这是因为我有一个别名在调用另一种语言,而不是bash。这就是给人的怪异$a*100/$c")\n"'上面的输出。但这可以忽略。

最后,以下是将所有这些放在一起并在所有默认文件以及您的默认文件正在采购的所有文件中搜索功能名称的方法:

grep_function(){
  target="$@"
  files=( ~/.bashrc ~/.profile ~/.bash_profile ~/.bash.login 
         ~/.bash_aliases /etc/bash.bashrc /etc/profile 
         /etc/profile.d/* /etc/environment)
    while IFS= read -r file; do
      files+=( "$file" )
    done < <(grep -hPo '(^|\s)(\.|source)\s+\K\S+'  "${files[@]}" 2>/dev/null)
    for file in "${files[@]}"; do
      ## The tilde of ~/ can break this
      file=$(sed 's|~/|'"$HOME"'/|g' <<<"$file")
      if [[ -e $file ]]; then
        grep -H "$target" -- "$file"
      fi
    done
}

将这些行添加到您的行中,~/.bashrc然后就可以运行(我fooBar以函数名称为例):

grep_function fooBar

例如,如果我的行中有~/.bashrc

. ~/a

文件~/a是:

$ cat ~/a
fooBar(){
  echo foo
}

我应该找到它:

$ grep_function fooBar
/home/terdon/a:fooBar(){

您的解决方案是一个很好的教学脚本示例,但是我发现bashity mcbashface的解决方案更简单。
佳诺

@jarno,它要简单得多!:)
terdon

阵列支持+=
D. Ben Knoble

@ D.BenKnoble他们做什么?您的意思不是array+="foo"将字符串附加foo到数组的第一个元素上?
terdon

1
@ D.BenKnoble谢谢!我不知道bash数组支持该表示法!
terdon

2

通常的每用户dotfile bash读取是~/.bashrc。但是,它很可能会找到其他文件,例如,我喜欢将别名和函数保存在名为~/.bash_aliases和的单独文件中~/.bash_functions,这使查找它们变得更加容易。您可以使用.bashrc以下source命令搜索命令:

grep -E '(^\s*|\s)(\.|source)\s' /home/USERNAME/.bashrc

获得用户创建文件的列表后,您可以.bashrc通过一次grep调用搜索它们和用户的文件,例如,foo查找我的设置功能:

grep foo /home/USERNAME/.bash{rc,_aliases,_functions}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.