Shellで定義済みの関数を流し読みしながら表示する

ShellScriptでドヤりたいGWアドベントカレンダー4日目いくで。

皆さん.zshrcや.bashrcは充実していますか?
Vimの世界では「.vimrcの量=vim力」と言われているらしいですが、shellも似たようなものでしょうか。

それはさておきshellには様々な関数がありますよね。zshやbashで標準で組み込まれている関数やzshrcなどに書き込んだ自作の関数。

「あの関数どうやって書いてたかなあ」とか「あれってどうやって実装されてるんだ?」といったときに便利なやつを作ってみた。

流し読みしながら気になる関数の中身を出力する

https://rasukarusan.github.io/blog-assets/shell_show_func/demo.gif
関数の中身をチラ見しながら選べる

みんな大好きfzfを利用しています。
左側に定義済みの関数名、右側にその中身をプレビューで出力しています。
ENTER押したらその内容を出力するものです。 ちなみにこれ地味に苦労した。。。どこに詰まったかは後述する。

とりあえず最終形だけ載せておきますね。
.zshrcに記載してください。bashの人はsource .zshrcのところを.bashrcに書き換えてね

function func() {
    local func=$(
       typeset -f \
       | grep ".*() {$" \
       | grep "^[a-z_]" \
       | tr -d "() {"   \
       | fzf --height 100% --preview "source ~/.zshrc; typeset -f  {}"
   )
    if [ -z "$func" ]; then
        return
    fi
    typeset -f $func
}

定義済み関数を全て出力する

これはtypeset -fで一発ですね。

$ typeset -f 

_SUSEconfig () {
    # undefined
    builtin autoload -XUz
}
__phpbrew_load_user_config () {
    if [[ -f $PHPBREW_HOME/init ]]
    then
        . $PHPBREW_HOME/init
        __phpbrew_set_path
    fi
}
...

zshだと同様の出力をするfunctionsという関数があります。

$ functions

_SUSEconfig () {
    # undefined
    builtin autoload -XUz
}
__phpbrew_load_user_config () {
    if [[ -f $PHPBREW_HOME/init ]]
    then
        . $PHPBREW_HOME/init
        __phpbrew_set_path
    fi
}
....

ただfunctions関数はman zshbuiltinsで見てみると、

functions [ {+|-}UkmtTuWz ] [ -x num ] [ name ... ]
functions -M [-s] mathfn [ min [ max [ shellfn ] ] ]
functions -M [ -m pattern ... ]
functions +M [ -m ] mathfn ...
      Equivalent to typeset -f, with the exception of the -x, -M  and  -W
      options.   For  functions  -u and functions -U, see autoload, which
      provides additional options.
      ....

Equivalent to typeset -fとなっているのでtypeset -fと同じであることがわかります。

関数名だけ出力する

シンプルにgrepの正規表現で抜き取ります。

$ typeset -f \
| grep ".*() {$" \
| grep "^[a-z_]" \
| tr -d "() {"   \

_SUSEconfig
__phpbrew_load_user_config
__phpbrew_reinit
__phpbrew_remove_purge
__phpbrew_set_lookup_prefix
__phpbrew_set_path
...

たぶんもっと良い正規表現があると思いますが、とりあえずこれで十分でしょう。

fzfで関数の中身をpreviewする

ここがめちゃくちゃ詰まったんですよねえ・・・ 関数の中身を出力するには

$ typeset -f 関数名

で出力できるので、普通にfzf --preview 'typeset -f {}'でイケるやろ!と思ってました。下のような感じで。

typeset -f \
| grep ".*() {$" \
| grep "^[a-z_]" \
| tr -d "() {"   \
| fzf --preview 'typeset -f {}'

するとね、何も表示されないんですよ。。。
なんでや!?と思いxargsで渡す方法にしてみた。

fzf --preview 'echo {} | xargs typeset -f'

すると以下のエラー文がプレビューに表示された。

xargs: typeset : No such file or directory

なんですかこれ・・・

これ調べてみると割と既知のエラーらしく、どうやらxargsにshellの関数を渡すときは一工夫しないといけないらしい。 詳しくは以下のサイトが参考になる。

stackoverflow.com unix.stackexchange.com

ということでどうしようかと色々調べていると、fzfのissuesに同じ質問があった。

github.com


Bash functions are not visible to child processes if you don't explicitly export them.

どうやら子プロセスではshell関数って表示されないみたい。定義されていないとみなされるのかな?

ということでissuesにも載っているfzfのpreview内でsource ~/.zshrcすることで落ち着いた。

$ typeset -f \
| grep ".*() {$" \
| grep "^[a-z_]" \
| tr -d "() {"   \
| fzf --height 100% --preview "source ~/.zshrc; typeset -f {}"

f:id:rasukarusan:20190426232719p:plain
preview画面に関数の中身を表示

やったぜ。(ただ毎回sourceするからちょっと遅い)

終わり

最初思いついたときはすぐ出来るだろと思っていたが、思わぬところで詰まった。
ただこういうところからコマンドの知識が増えていくのがShellScriptの醍醐味ではある。