Como posso encontrar onde determinada função bash está definida?


Respostas:


32

Ative a depuração. A partir do manual Bash :

extdebug

Se definido na chamada do shell ou em um arquivo de inicialização do shell, organize a execução do perfil do depurador antes do início do shell, idêntico à --debuggeropção. Se definido após a invocação, o comportamento destinado ao uso por depuradores será ativado:

  • A -Fopção para o built-indeclare (consulte Bash Builtins ) exibe o nome do arquivo de origem e o número da linha correspondente a cada nome de função fornecido como argumento.

Exemplo:

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

E realmente:

$ 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()

Esta é uma solução excelente e simples. Quanto ao seu exemplo, não é mesmo necessário para iniciar um novo shell: shopt -s extdebug; declare -Ff quote; shopt -u extdebug.
jarno

2
@jarno ah, bem, ao contrário do meu nome, eu uso o zsh. Por isso, comecei um novo shell. : D
bashity mcbashface

Existe um método semelhante para encontrar locais de declaração de alias ?
FedonKadifeli

@fedon minha abordagem complicada e complicada abaixo deve funcionar.
terdon

1
Você poderia fazer isso uma função como esta: find_function()( shopt -s extdebug; declare -F "$@"; ). Com os parênteses no corpo da função, ele é executado em um subshell e a alteração para shopts não afeta o chamador. E o -fnão parece ser necessário.
wjandrea

15

Isso é realmente mais complicado do que parece à primeira vista. Quais arquivos são lidos pelo seu shell depende do tipo de shell que você está executando no momento. Seja interativo ou não, seja um shell de logon ou não-logon e qual combinação dos itens acima. Para pesquisar em todos os arquivos padrão que podem ser lidos pelos diferentes shells, você pode fazer (mude $functionNamepara o nome real da função que está procurando):

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

Se isso não funcionar, você pode estar chamando um arquivo não padrão usando .ou seu alias source. Para encontrar esses casos, execute:

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

Isso provavelmente precisa de alguma explicação. Ele -Phabilita PCRE (Perl Compatible Regular Expressions), que nos permite usar alguma sintaxe de regex mais sofisticada. Especificamente:

  • (^|\s): corresponde ao início de uma linha ( ^) ou espaço em branco ( \s).
  • (\.|source)\s+: corresponde a um .caractere literal ( \.) ou à palavra source, mas somente se forem seguidos por um ou mais caracteres de espaço em branco.

Aqui está o que isso me dá no meu sistema:

$ 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

Como você pode ver, no entanto, isso imprimirá toda a linha correspondente. O que realmente interessa é a lista de nomes de arquivos chamados, não a linha que os chama. Você pode obter aqueles com este regex mais complicado:

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

O -hsinalizador suprime a impressão dos nomes dos arquivos onde uma correspondência foi encontrada, o que grepocorre por padrão quando solicitado a pesquisar em vários arquivos. Os -omeios "apenas imprimem a parte correspondente da linha". O material extra adicionado ao regex é:

  • \K: ignore qualquer coisa que corresponda a esse ponto. Esse é um truque do PCRE que permite usar um regex complexo para encontrar sua correspondência, mas não incluir a parte correspondente ao usar o -osinalizador grep .

No meu sistema, o comando acima retornará:

$ 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

Observe que, por acaso, uso um .seguido de um espaço que não é usado para fornecimento, mas é porque eu tenho um alias que está chamando outro idioma, não o bash. Isso é o que dá o estranho $a*100/$ce ")\n"'na saída acima. Mas isso pode ser ignorado.

Por fim, veja como reunir tudo isso e procurar um nome de função em todos os arquivos padrão e em todos os arquivos que seus arquivos padrão estão procurando:

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
}

Adicione essas linhas ao seu ~/.bashrce você poderá executar (estou usando fooBarcomo exemplo o nome da função):

grep_function fooBar

Por exemplo, se eu tiver essa linha no meu ~/.bashrc:

. ~/a

E o arquivo ~/aé:

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

Eu deveria encontrá-lo com:

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

Sua solução é um ótimo exemplo de script educacional, mas acho a solução do bashity mcbashface mais simples.
jarno

@jarno e é muito, muito mais simples! :)
terdon

Matrizes de suporte+=
D. Ben Knoble

@ D.BenKnoble eles fazem? Você quer dizer além de array+="foo"anexar a string fooao 1º elemento da matriz?
terdon

1
@ D.BenKnoble obrigado! Eu não sabia que os arrays do bash suportavam essa notação!
terdon

2

O habitual por usuário dotfile bashlê é ~/.bashrc. No entanto, pode muito bem fonte outros arquivos, por exemplo, eu gosto de manter aliases e funções em arquivos separados chamados ~/.bash_aliasese ~/.bash_functions, o que torna muito mais fácil encontrá-los. Você pode pesquisar o .bashrcpara sourcecomandos com:

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

Depois de ter a lista de arquivos criados pelo usuário, você pode procurá-los e o usuário .bashrccom uma única grepchamada, por exemplo, a função foopara minha configuração:

grep foo /home/USERNAME/.bash{rc,_aliases,_functions}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.