Comment puis-je trouver où certaines fonctions bash sont définies?

28

Il existe de nombreuses fonctions qui peuvent être utilisées dans le shell Bash. Leurs définitions peuvent être répertoriées par set, mais comment trouver dans quels fichiers certaines fonctions définies par l'utilisateur sont définies?

jarno
la source
1
Regardez: askubuntu.com/questions/463462/…
FedonKadifeli

Réponses:

32

Activez le débogage. Du manuel Bash :

extdebug

Si défini à l'invocation du shell, ou dans un fichier de démarrage du shell, organisez l'exécution du profil de débogage avant le démarrage du shell, identique à l' --debuggeroption. S'il est défini après l'invocation, le comportement destiné à être utilisé par les débogueurs est activé:

  • L' -Foption de la fonction declareintégrée (voir Bash Builtins ) affiche le nom du fichier source et le numéro de ligne correspondant à chaque nom de fonction fourni en argument.

Exemple:

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

Et en effet:

$ 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()
bashity mcbashface
la source
Il s'agit d'une excellente solution simple. Quant à votre exemple, il est même pas nécessaire de lancer un nouveau shell: shopt -s extdebug; declare -Ff quote; shopt -u extdebug.
jarno
2
@jarno ah, contrairement à mon nom, j'utilise zsh. C'est pourquoi j'ai commencé un nouveau shell. : D
bashity mcbashface
Existe-t-il une méthode similaire pour rechercher les emplacements de déclaration d' alias ?
FedonKadifeli
@fedon mon approche compliquée et compliquée ci-dessous devrait fonctionner.
terdon
1
Vous pouvez faire cette fonction comme ceci: find_function()( shopt -s extdebug; declare -F "$@"; ). Avec les parens sur le corps de la fonction, il est exécuté dans un sous-shell et le changement de shopts n'affecte pas l'appelant. Et -fcela ne semble pas nécessaire.
wjandrea
15

C'est en fait plus compliqué qu'il n'y paraît au premier abord. Les fichiers lus par votre shell dépendent du type de shell que vous utilisez actuellement. Qu'il soit interactif ou non, qu'il s'agisse d'un shell de connexion ou non, et quelle combinaison des éléments ci-dessus. Pour rechercher dans tous les fichiers par défaut qui peuvent être lus par les différents shells, vous pouvez faire (changer $functionNamele nom réel de la fonction que vous recherchez):

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

Si cela ne fonctionne pas, vous appelez peut-être un fichier non par défaut à l'aide de .ou de son alias source. Pour rechercher de tels cas, exécutez:

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

Cela nécessite probablement quelques explications. Le -Ppermet Perl Compatible Regular Expressions (PCRE) qui nous permet d'utiliser une syntaxe regex plus sophistiquée. Plus précisément:

  • (^|\s): correspond au début d'une ligne ( ^) ou d'un espace ( \s).
  • (\.|source)\s+: correspond soit à un .caractère littéral ( \.) soit au mot source, mais uniquement s'ils sont suivis par un ou plusieurs espaces.

Voici ce que cela me donne sur mon système:

$ 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

Comme vous pouvez le voir, cependant, cela imprimera toute la ligne correspondante. Ce qui nous intéresse vraiment, c'est la liste des noms de fichiers appelés, pas la ligne qui les appelle. Vous pouvez les obtenir avec ce regex plus compliqué:

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

L' -hindicateur supprime l'impression des noms de fichiers où une correspondance a été trouvée, ce qui est grepfait par défaut lorsqu'on lui demande de rechercher dans plusieurs fichiers. Le -omoyen "n'imprime que la partie correspondante de la ligne". Les éléments supplémentaires ajoutés à l'expression régulière sont les suivants:

  • \K: ignorer tout ce qui correspond à ce point. Il s'agit d'une astuce PCRE qui vous permet d'utiliser une expression rationnelle complexe pour trouver votre correspondance, mais pas d'inclure cette partie correspondante lors de l'utilisation du -odrapeau de grep .

Sur mon système, la commande ci-dessus renverra:

$ 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

Notez que j'ai une utilisation de .suivie d'un espace qui n'est pas utilisé pour le sourcing mais c'est parce que j'ai un alias qui appelle une autre langue, pas bash. C'est ce qui donne l'étrange $a*100/$cet ")\n"'dans la sortie ci-dessus. Mais cela peut être ignoré.

Enfin, voici comment rassembler tout cela et rechercher un nom de fonction dans tous les fichiers par défaut et tous les fichiers que vos fichiers par défaut proviennent:

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
}

Ajoutez ces lignes à votre ~/.bashrcet vous pouvez ensuite exécuter (j'utilise fooBarcomme exemple de nom de fonction):

grep_function fooBar

Par exemple, si j'ai cette ligne dans mon ~/.bashrc:

. ~/a

Et le fichier ~/aest:

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

Je devrais le trouver avec:

$ grep_function fooBar
/home/terdon/a:fooBar(){
terdon
la source
Votre solution est un excellent exemple de script éducatif, mais je trouve la solution de bashity mcbashface plus simple.
jarno
@jarno et c'est beaucoup, beaucoup plus simple! :)
terdon
Prise en charge des tableaux+=
D. Ben Knoble
@ D.BenKnoble font-ils? Vous voulez dire autre que l' array+="foo"ajout de la chaîne fooau 1er élément du tableau?
terdon
1
@ D.BenKnoble merci! Je ne savais pas que les tableaux bash supportaient cette notation!
terdon
2

Le fichier dotfile habituel par utilisateur bashest ~/.bashrc. Cependant, il peut très bien source d'autres fichiers, j'aime par exemple garder les alias et les fonctions dans des fichiers séparés appelés ~/.bash_aliaseset ~/.bash_functions, ce qui facilite leur recherche. Vous pouvez rechercher .bashrcdes sourcecommandes avec:

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

Une fois que vous avez la liste des fichiers créés par l'utilisateur, vous pouvez les rechercher ainsi que l'utilisateur .bashrcavec un seul grepappel, par exemple pour la fonction foopour ma configuration:

grep foo /home/USERNAME/.bash{rc,_aliases,_functions}
dessert
la source