[fish] 展開されるalias - abbr

![top2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167409/c8b35ee6-96c8-0d54-faef-7afc5e145c89.gif)

[dotfiles Advent Calendar 2019](https://qiita.com/advent-calendar/2019/dotfiles) 22日目の記事です。

この記事で触れるdotfilesは https://github.com/wataash/dotfiles の以下のファイルです。

- [.config/fish/config.fish](https://github.com/wataash/dotfiles/blob/master/.config/fish/config.fish)
- [.config/fish/config_abbr.fish](https://github.com/wataash/dotfiles/blob/master/.config/fish/config_abbr.fish)

## fishの `abbr` とは

(manual: [abbr - manage fish abbreviations](https://fishshell.com/docs/current/commands.html#abbr))

「**展開されるalias**」です。[aliasよりも "fishy" [^1] と評され](https://github.com/fish-shell/fish-shell/issues/731)、aliasよりも優れた面があります。

[^1]: [fishのデザイン](https://fishshell.com/docs/current/design.html)に沿っていてイケている、くらいの意味

\# @yutakatay さんのコメントの通り、zshでも使えるそうです。

`gs` を `git status` に展開するabbrを考えてみましょう。以下のコマンドでabbrを登録します：

```sh
abbr -a gs git status
# aliasだと
# alias gs='git status'
```

これで `gs` という文字列が `gs(space)` または `gs(enter)` で展開されるようになります。

![gs.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167409/c6bb43a4-e8e7-0135-878f-e14c7b9ae294.gif)

展開されるのはargv[0]のみです。コマンド名 (argv[0]) の位置でのみ展開され、引数 (arvg[1..]) では展開されません。

![gsgsgs.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167409/8d8e30d9-4196-493f-dd5e-3c6813a80968.gif)

`/usr/bin/gs` (Ghostscript) を使いたくなったら展開を避けるようにタイプすれば良いです。（`command gs` でも良いです）

![ghost.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167409/5f947bfc-a778-9f0b-916a-965ac549e2de.gif)

## aliasに比べた利点

### historyのgreppabilityが上がる

例として `git tag` を含む履歴を検索してみます。<kbd>G</kbd> <kbd>T</kbd> <kbd>Space</kbd> <kbd>Ctrl-R</kbd>：

![gt.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167409/3cf963c5-6154-f5aa-7cbe-381238ffc831.gif)

\# <kbd>Ctrl-R</kbd> にはpecoによる検索を割り当てています（後述）

このように問題なく履歴検索ができていますが、これは `alias gt='git tag'` だと難しいでしょう。`git tag` とは関係のない `gt` を含む履歴が大量にあるからです。(`gtest`, `gtop`, `libgtk`, ...)

また、alias名は変更してしまうと履歴検索の互換性(?)が失われてしまいます。例えば `git status` のalias `gist` を `gst` に変更してしまうと、今までの履歴は `gist`、今後は `gst` として残るので、両者を同時に検索できなくなります。abbrの場合は両者とも同じ `git status` として展開されるためこの問題はありません。

### 引数の削除・変更ができる

例：

```sh
abbr -a l  ls -hiFl
# alias l='ls -hiFl'

l
↓ 展開
ls -hiFl
↓ チャットに貼り付けるときは -h -i は削除
ls -Fl
```

このようにオプションを変更することはaliasにはできません。

### 視認性が高い

[Mozilla rrのチュートリアル](https://github.com/mozilla/rr/wiki/Usage)をやっているときのことでした。

```sh
$ rr record /bin/echo foo
↓ abbr展開
$ rm -rf record /bin/echo foo
```

あぶねー！！
`abbr -a rr rm -rf` は1日10回とか使うので絶対忘れないはずなんですが、このときは頭から抜けていました。こわっ [^2]
aliasだったら気づかずに実行していたかもしれません。（`sudo` 付いてなかったのでこの場合 `/bin/echo` はセーフですが。。）

[^2]: `abbr -a rr rm -rf` は流石にアグレッシブすぎるのでやめました。

あと他の人に端末操作を見せるときが楽です。aliasを使うと都度説明が必要になっちゃいますよね。あと将来の自分がhistoryを見たときにaliasが理解できなくなっているかもしれない？

## abbr tips

### abbrを思い出す

`abbr | wc -l` したら412個ありました。当然忘れます。個人的にやっているabbrの思い出し方を紹介します。

1. `git` を含むabbrを探す
  - `abbr | peco` で `git` で検索すれば良いです。`abbr | peco` もよく使うので `abp` としてabbrしています。
1. `gs` で始まるabbrをリスト
  - <kbd>G</kbd> <kbd>S</kbd> <kbd>Ctrl-I</kbd> <kbd>Ctrl-S</kbd> <kbd>V</kbd> <kbd>Space</kbd>
    - <kbd>Ctrl-I</kbd> は <kbd>Tab</kbd> と同じ
    - <kbd>Ctrl-S</kbd> でSearch
    - abbreViation の V で検索。これだけでほぼabbrに絞れる
    - ![search.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167409/fbdbf843-1028-4110-c5ea-e8818495d1da.gif)

### 読み込まれていないときだけabbr読み込み

私のabbrはすべて読み込むのに1秒くらいかかります。fish起動のたびに読み込むには長すぎます。
ところでabbrの実体は[universal variable](https://fishshell.com/docs/current/index.html#variables)です。
 `abbr -a gs git status` がやっていることは `set -U _fish_abbr_gs 'git status'` と同じです。（space、enterを押したときの展開は、universal variablesと比較して判定する実装になっています）

これを利用して、`gs` というabbrが登録されているときだけ = `_fish_abbr_gs` というユニバーサル変数が登録されているときだけ、abbr一覧を読み込むようにしています。

```config.fish
if set -qU _fish_abbr_gs && source ~/.config/fish/config_abbr.fish
```

ユニバーサル変数なので明示的に消さないとabbrは残り続けます。リロードするには一旦全てのabbrを消してから読み込み直す必要があります。

```sh
for a in (abbr --list); abbr --erase $a; end
source ~/.config/fish/config_abbr.fish
```

これもabbrにしておくと良いでしょう。

```sh
abbr -a abe 'for a in (abbr --list); abbr --erase $a; end'
abbr -a abs "source ~/.config/fish/config_abbr.fish"
```

### グローバルalias

fishではzshのグローバルエイリアスは[実装しない方針](https://github.com/fish-shell/fish-shell/issues/1963)になっています。zshのグローバルaliasでの

```zsh
alias -g L='| less'
foo L  # -> foo | less
```

のようなパイプと同じことをfishでやるには `bind` と `commandline` を使います ([by @GReagle@github](https://github.com/fish-shell/fish-shell/issues/1963#issuecomment-93775067))：

```sh
# by @GReagle@github
# https://github.com/fish-shell/fish-shell/issues/1963#issuecomment-93775067
function bind_global_alias
        switch (commandline -t)
  case "l"
    commandline -rt '| less'
  case "h"
    commandline -rt '| head'
  case "t"
    commandline -rt '| tail'
  case "g"
    commandline -rt '| grep'
  case "w"
    commandline -rt '| wc'
  case "cc"
    commandline -rt '| ccze -A'
  end
end

bind \cx bind_global_alias
```

これで <kbd>L</kbd> <kbd>Ctrl-X</kbd> は `| less` に展開されるようになります。

![bind.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167409/4f9f4230-161f-5498-d762-1d2f3a4cb547.gif)

`abbr` コマンドも `commandline` を使って実装されているので似たような動作になっています。greppabilityが高いという点もabbrと似ていますね。fish哲学の ["The law of orthogonality"](https://fishshell.com/docs/current/design.html) を感じます。

### pecoによる履歴の絞り込み

私のfishの履歴は10万行近くあります。[^3]

[^3]: このままだと[上限の256Ki行](https://github.com/fish-shell/fish-shell/issues/2674)を越えそうだ…
```
ls -hFl ~/.local/share/fish/fish_history  # 7.1MiB
history wc -l                             # 91660
```

標準の <kbd>Ctrl-R</kbd> で履歴を遡るのはしんどいです。[peco](https://github.com/peco/peco)と[oh-my-fish/plugin-peco](https://github.com/oh-my-fish/plugin-peco)を入れておきましょう。[^4] [^5]

[^4]: fzfによる履歴検索は個人的に相性が悪いと感じました。曖昧に検索したい場面があまり無かった。他の方はどうでしょうか？

[^5]: oh-my-fish/plugin-peco は [oh-my-fish](https://github.com/oh-my-fish/oh-my-fish) でのインストールが想定されていますが、個人的にomfは推奨しません。omfを入れるとプロンプトに時刻を表示するようになるのですが、ターミナル横幅の計算が間違っていて長いコマンドとかfull-width charactersを打つとぐちゃぐちゃになる。

```sh
sudo apt install peco
yum install peco  # 未確認
brew intsall -v peco

# go getの場合：
# pecoでは go get は非推奨ですが、多くの人にとって推奨方法の glide install は面倒でしょう…
# 私はgo getで入れてしまっています。
go get -v -u github.com/peco/peco

# https://github.com/jorgebucaran/fisher
curl https://git.io/fisher --create-dirs -sLo ~/.config/fish/functions/fisher.fish

# https://github.com/oh-my-fish/plugin-peco
fisher add oh-my-fish/plugin-peco
```

```config.fish
function fish_user_key_bindings
  bind \cr 'peco_select_history (commandline -b)'
end
```

### コマンドにコメントをつけておく

abbrとは直接関係無いですが、パイプをたくさん使ったシェル芸には `long | long | command  # summary comment` とコメントをつけておくのがオススメです。pecoでコメントを検索できるようになるのと、コメントを読んだだけで何のコマンドか思い出せるようになります。

例：git tagをすべて削除する `git tag | xargs git tag -d  # delete remove all local tags`
![gtdelete.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167409/0df86dec-b25c-0dab-e33f-f2b79bfa5cfa.gif)

## まとめ

abbr使いましょう！！！！