Bash で alias_method_chain を実装する

Ruby on Rails (ActiveSupport) の alias_method_chain[^alias_method_chain_is_deprecated] を Bash で使えるように実装しました。ソースコードは GitHub (https://github.com/suzuki-kei/bash-alias-method-chain) で公開しています。

[^alias_method_chain_is_deprecated]: alias_method_chain は Ruby on Rains 5 で非推奨になりましたが、実現したいことが伝わりやすいと思い alias_method_chain と書きました。

# (Ruby) alias_method_chain とは

Ruby on Rails (ActiveSupport) の `alias_method_chain` を使うと、既存のメソッドの前後に処理を追加できます:

```ruby
module Hello

    def hello(name)
        puts "Hello #{name}"
    end

    def hello_with_trace(name)
        puts '---- BEGIN ----'
        hello_without_trace(name)
        puts '---- END ----'
    end

    # hello_without_trace は元の hello のエイリアスになり,
    # 新しく hello は hello_with_trace のエイリアスになる.
    alias_method_chain :hello, :trace

end
```

`hello` と呼び出すと `hello_with_trace` が実行されます:

```text
> include Hello
> hello('Taro')
---- BEGIN ----
Hello Taro
---- END ----
```

# (Bash) alias_method_chain

以下のように実装しました。

```bash:alias-method-chain.sh
# スクリプト中で alias を使用する場合に必要.
shopt -s expand_aliases

function alias_method_chain {
    local -r name="$1"
    local -r functionality="$2"

    case $(type -t "$name") in
        alias)
            eval "$(cat <<EOS
                function ${name}_without_${functionality} {
                    $(type "$name" | sed -r "s/^$name is aliased to \`|'$//g") "\$@"
                }
                builtin alias ${name}=${name}_with_${functionality}
EOS)"
            return 0
            ;;
        builtin | file | function)
            eval "$(cat <<EOS
                function ${name}_without_${functionality} {
                    ${name} "\$@"
                }
                builtin alias ${name}=${name}_with_${functionality}
EOS)"
            return 0
            ;;
        *)  
            echo "ERROR: $(type -t '${name}') is unsupported type." >&2
            return 1
            ;;
    esac
}
```

使用例:

```bash:alias-method-chain-test.sh
source alias-method-chain.sh

# スクリプト中で alias を使用する場合に必要.
shopt -s expand_aliases

function hello {
    echo "Hello $1" 
}   

function hello_with_trace {
    echo '---- BEGIN ----'
    hello_without_trace "$@"
    echo '---- END ----'
}   

alias_method_chain hello trace

hello 'Taro'
```

実行結果:

```text
---- BEGIN ----
Hello Taro
---- END ----
```

# 解説

## スクリプト中で alias を使う方法

Bash スクリプトの中で `alias` を使う場合は以下の指定が必要です。

```bash
# スクリプト中で alias を使用する場合に必要.
shopt -s expand_aliases

...
```

## type の結果で分岐する

Bash のビルトイン関数である `type` を使うと、指定した名前がどのように解釈されるのか調べることができます。

```text
$ type bash
bash is /bin/bash
```

`-t` オプションで種類が分かります。`type -t` の結果は `alias`, `keyword`, `function`, `builtin`, `file` もしくは空文字列になります。

```text
$ type -t bash
file
```

この結果で `alias_method_chain` の処理を分岐します（種類によって実装を変える必要があるため）。

## alias を置き換える

alias を置き換える実装は以下のようにしました。これは `type` の出力結果に依存してしまっているので不本意です。他に良い方法があれば改善したい部分です。

```bash
...
    case $(type -t "$name") in
        alias)
            eval "$(cat <<EOS
                function ${name}_without_${functionality} {
                    $(type "$name" | sed -r "s/^$name is aliased to \`|'$//g") "\$@"
                }
                builtin alias ${name}=${name}_with_${functionality}
EOS)"
            return 0
            ;;
...
```