fish shellでhistoryを使いこなす

## はじめに
もしかすると誰もが通る道なのかもしれないが、`history`コマンドを使いこなせたら作業早くなるかもなぁ、と思って調べた内容。（~~コマンド全て覚えるのはめんどうというのもある~~）
myPCが`fish`だったばかりに、`bash`とは違う情報がいろいろ出てきて面白かったので、まとめてみました。
これを読めば、

- `fish`の`history`の使い方について
- `fish`の`history`を使うときに調べるであろうことについて

知ることができると思います。

随所に、悪い癖で自分の思ったことをつらつらつらと書いた箇所があるので[私がなぜにfishだったのか？](#私がなぜにfishだったのか)・[「ユーザーフレンドリ」なものに悪いものはいない？](#「ユーザーフレンドリ」なものに悪いものはいない？)など、時間ない人は読み飛ばしちゃってください。
  
## 私がなぜに`fish`だったのか
もちろん、myPCに`fish`を入れた犯人は私です。
一番の理由は、新入社員だった頃に、若手の先輩に激推しされたことでした。
聞くところによると、「ユーザーフレンドリ」なシェルらしいということで、「ユーザーフレンドリ」なものに悪いものはないだろうと入れました。
今回、`fish`の`history`を調べたことで、`fish`の概念のようなものも、ちょっとは知ることができた気がするので、そこいらへんも少し書きたいと思います。
## `bash`の`history`との違い
### 書式とオプション（サブコマンド）の違い
一番大きな違いは、「`サブコマンド`という概念」と、「コマンドのオプションの内容」かと思います。
まずは、比較のため`bash`の`history`の書式と主なオプションについておさらいしてみましょう。

**bashの場合**

```
history [オプション]
history オプション [ファイル]
```


|オプション  |意味  |
|---|---|
|整数値  |履歴の末尾から指定した行数分を表示  |
|-c  |履歴一覧から全ての項目を削除  |
|-d 番号  |指定した番号の履歴項目を削除  |
|-a  |履歴ファイルに現在のセッションの履歴を追加  |
|-n  |履歴ファイルから読み込まれていない行を全読み込み  |
|-r ファイル  |履歴ファイルを読み込み、内容を履歴一覧に追加  |
|-w ファイル  |現在の履歴を履歴ファイルに上書き  |

次は`fish`の`history`についてですが、`サブコマンド`の影響で、`bash`の`history`よりも書式が多彩になっています。

**fishの場合**

```
# 検索文字列にマッチする履歴を出力
history search [オプション] ［ 'search string'... ］

# 指定された文字列に該当する履歴のエントリを削除
history delete [オプション] 'search string'...

# 他のfishセッションからの履歴の更新をマージ
history merge

# すべての更新を履歴ファイルに書き込み(自動的に保存されるのでほぼ使用しない)
history save

# 履歴ファイルをクリア
history clear

# 全履歴を出力（結果的には history = history search となる）
history
```
`サブコマンド`とは`history`に続く、
`search`、`delete`、`merge`、`save`、`clear` の部分です。
それぞれ上記で示した用途で利用されます。

|オプション  |意味  |対応しているサブコマンド  |
|---|---|---|
|-C / --case-sensitive  |大文字小文字を区別  |`search`/`delete` |
|-c / --contains  |指定された文字列に部分一致  |`search`/`delete`　|
|-e / --exact  |指定された文字列に完全一致  |`search`/`delete`　|
|-p / --prefix  |指定された文字列に前方一致  |`search`/`delete`　|
|-t / --show-time  |`history`項目の日時表示  |`search`/`delete`　|
|-z / --null  |改行の代わりにヌル文字を表示の区切りとして使用  |`search` |
|-`<number>` / -n `<number>`  |`<number>`にマッチした数だけ`history`項目を表示  |`search` |
|-R / --reverse  |新しい順から古い順に変更  |`search` |

補足
-`<number>` / -n `<number>`は他に--max=`<number>`という書き方もあります。
`history = history search`のため、`search`で利用できるオプションは`history`でも利用できます。

何もかも違うと言って過言じゃないですが、
何となく違いを捉えるなら**「fishの方がhistoryの検索に長(た)けている」**という理解で大丈夫だと思います。

少し自分なりの書き方をしている部分もありますので、正確な情報は以下を参照してください。
[公式サイトのhistoryのページ](http://fishshell.com/docs/current/commands.html#history)
[全訳！fishシェル普及計画のhistoryのページ](http://fish.rubikitch.com/history/)


### 使い方の違い
サブコマンド`search`、`delete`の使い方について一例を示してみます。
例) `which`コマンドでパスの確認を行った履歴を日時とともに表示する。

```
history search -t which

# 出力
# 金  8/ 9 20:41:17 2019
which pyenv
# 金  8/ 9 20:40:39 2019
which node
# 金  8/ 9 20:40:11 2019
history which
# 水  5/29 00:17:15 2019
which fish
# 水  5/15 21:16:26 2019
which python
# 水  5/15 20:38:13 2019
which bash
```
なんだったら、（さっきからしつこく書いてますが）`history = history search`だと使っていたら分かるので、`search`も書かなくていいです。`history`はデフォルトで`grep`の機能もくっついてるようなものだと思ってもらったらいいと思います。

例)`which`コマンドの履歴の一部を削除する

```
history delete -p which

#出力
[1] which pyenv
[2] which node
[3] which fish
[4] which python
[5] which bash

Enter nothing to cancel the delete, or
Enter one or more of the entry IDs separated by a space, or
Enter "all" to delete all the matching entries.

Delete which entries? >
```
`delete`のサブコマンドを使うと、上記のような出力結果が得られます。
検索した全ての履歴を削除したい場合は`all`をタイプしてEnterしたらいいし、`which node`と`which fish`を削除したいなら、`2 3`とタイプしてEnterしたらOKです。

`delete`を使って複数の検索結果を得たい場合は、基本的に部分一致の`-c / --contains`か前方一致の`-p / --prefix`のオプションをつけます。

（余談）
公式ドキュメントを読む限りは、`delete`のデフォルトのオプションは`-e / --exact`ですが、挙動および公開されているソースを確認すると`-c / --contains`になっています。ドキュメントとソースどちらが間違っているか、今のところはっきりとしていません。

### 表示の挙動の違い
`bash`と比べて以下のような表示の違いがあります。

-  `history`の項目が、新しい順で表示される。
-  （コマンドラインの）1画面ずつ`history`の項目が表示される。（デフォルトで`history | more`を実行したようなイメージ）

確かに、古い順で全部表示されるより、新しい順で画面に出力できる範囲で表示された方が便利ですよね。（流石「ユーザフレンドリ」）

## `bash`でよくやる設定について
さてさて、`fish`と`bash`の`history`の違いはよく分かった、お腹いっぱいとなった私。
今度は`fish`の`history`を使いこなしたい（便利に使いたい）！と思い立つことになります。
そして私は気づくのです。「`bash`だと`history`を便利にするために、いろいろと設定するよな？」ということに。
つまり、`bash`で設定することを`fish`だったらどうやって設定するんだろうと思ったわけです。

以下、いろいろと調べましたが、設定できなかったものもありますのでご注意ください。

### コマンド履歴の保存件数を増やす
デフォルトの500個は少なすぎる！ってことで`bash`だと一番最初に設定するのではないでしょうか。お馴染み`HISTSIZE`に値を設定する作業。

`fish`はどうやって設定するのか？
答えは、なんと**設定できない**という落ちになります。
実は、**fishにHISTSIZEなんてものはない**
デフォルトで256k個保存できるから大丈夫っしょ？って感じ↓
https://github.com/fish-shell/fish-shell/issues/2674

そうなのか・・・。まあ256k個もあるならいいか・・・

### historyに日時を付与する
`bash`のノリでいくと、`HISTTIMEFORMAT`に日時の出力形式を設定する、という話になるわけだが、**HISTTIMEFORMATに類するものはfishには（これまた）存在しない。**
オプションの`-t / --show-time`を使いましょう、ということらしい。
  
オプションつけるのも面倒くさい！！という人には`alias`で設定するという方法があるかと思います。

**historyに日時を付与するaliasの設定方法（あくまで一例）**

```
 alias thistory "history --show-time='%Y-%m-%d %H:%M:%S '"
 funcsave thistory
```
表示形式の変更する場合は、基本的には`-t`の短縮形ではなく`--show-time`を使います。
上記で定義した`thistory`の実行結果は以下の通りです。

```
thistory -p which

# 出力
2019-08-09 20:41:17 which pyenv
2019-08-09 20:40:39 which node
2019-05-29 00:17:15 which fish
2019-05-15 21:16:26 which python
2019-05-15 20:38:13 which bash
```
ちなみにデフォルの表示形式だと[使い方の違い](#使い方の違い)で示したように、改行が入ります。
（上記の表示形式の方が慣れている人が多いかもしれません）

### 〜横道〜 aliasの話
[historyに日時を付与する](#historyに日時を付与する)で示した通り、`fish`における`alias`は**設定ファイルに記述するのではなく、コマンドラインで設定（alias）して保存（funcsave）することができます。**
「ユーザフレンドリ」としてはかなりの美点なのではないでしょうか。

が、しかし、、**元々存在する関数と同名の別名は定義できません。**とのこと。

やってみると、できてしまうのですが（苦笑）
挙動がおかしくて、戻し方を知らないと混乱するのでやらないのが無難です。

ちなみに`fish`の`alias`は中身的には`function`コマンドになります。

参考
[aliasのドキュメント（公式を元にした日本語翻訳版）](http://fish.rubikitch.com/alias/)
[functionのドキュメント（公式を元にした日本語翻訳版）](http://fish.rubikitch.com/function/)

また、`abbr`コマンドというものもあります。（どちらかというと、こちらの方が他のシェルの`alias`、別名定義に近いようです）↓

[abbrのドキュメント（公式を元にした日本語翻訳版）](http://fish.rubikitch.com/function/)

短縮形を管理するコマンドではありますが、`abbr`だと同名での定義もできます。
`abbr`と`alias`(`function`)の違いも、色々と試してみましたが、奥が深そうでした・・・。今回は内容書き出すと趣旨とどんどんずれていくので割愛します。

### 特定のコマンドの履歴を保存しない
`ls`や`history`コマンド自体など、別段`history`に残しておかなくていいコマンドを保存しておきたくない！！ってときの話。

まず前提ですが`fish`には以下のような特徴があります。

- そもそも**重複するコマンドの履歴は保存されない。**
- **先頭に空白を記述すると、履歴に保存されない**

上記がデフォルトの挙動です。
`history -e ll`とかしても結果は一つしか出てこないし、`ll`コマンドを実行する度に記録される時間は更新される、、といった具合です。

`bash`でいう、
`HISTCONTROL=ignoreboth`のような状態です。

さてさて、「特定のコマンドの履歴を保存しない」設定ですが、当然のように`fish`には`HISTCONTROL`や`HISTIGNORE`に類するものはありません・・・。

先人の知恵としては以下のようなものがあります。
(1) https://github.com/fish-shell/fish-shell/issues/2788
issueの回答として以下のような`function`が記載されています。

```
function ignorehistory --on-event fish_prompt # or maybe fish_preexec, see function --help
    history --delete fg bg
end
```
簡単な説明ですが、
上記の`function`の意味は「`fish_prompt`が実施される度に`history`の中の`fg`と`bg`のコマンドの履歴を削除する」ってな具合。
`fish_prompt`については[(超入門)fishでプロンプトを変えたい人へのチュートリアル](https://qiita.com/najayama/items/553329c242edc434b155)を参照ください。

「`fish_prompt`が実施される度」というのは、つまりは「コマンドラインを起動した・コマンドを実行する度」ということになります。

[使い方の違い](#使い方の違い)で示しましたが、サブコマンド`delete`は複数の削除候補が出てくると、全て削除するか一部を削除するか聞いてくるので、上記の`function`は場合によっては鬱陶しいんじゃないかなぁと個人的には思っています。（この挙動は上記`function`にオプション`--exact`を付与すると改善されるかもしれません）

(2) https://stackoverflow.com/questions/34001591/in-fish-is-it-possible-to-ignore-all-git-related-commands/34015787

[〜横道〜 aliasの話](#〜横道〜-aliasの話)で少し触れた`abbr`コマンドを使って、保存しておきたくないコマンドの先頭に空白を付与して別名保存します。

```
abbr -a history ' history'
```

**先頭に空白を記述すると、履歴に保存されない**という`fish`の挙動を利用した形です。
設定するならこちらの方がいいのではないかと思います。
参考サイト(2)の最後に書かれている関数については(1)と同じ理由で、個人的には非推奨です。

ちなみに
逆に、**重複するコマンドの履歴も保存できるようにする**術はなさそうです。

個人的には、重複するとは言え、「いつごろ、このコマンド使ったっけ？」というのを調べることもあるんじゃないかなぁ、、と思うのですが、調べた限りは見つかりませんでした。

### history expansion について
設定とは少し違いますが、、`history expansion`について、
`history expansion`とは`bash`などで`![行番号]`と指定すると、`history`の行番号に該当するコマンドが実行される、あの機能のことです。（`!!`打つと、直前のコマンドが実行されたり、、詳しくは→https://mseeeen.msen.jp/bash-history-expansion/）

`fish`には`![行番号]`の記述方法は・・・ない！（そもそもhistoryに行数ついてない・・・）

これは公式のFAQでも、言及されています。（すごく`fish`側のこだわりを感じるところだと、勝手に思っています）
[Why doesn't history substitution ("!$" etc.) work?](https://fishshell.com/docs/current/faq.html#faq-history)
[（日本語版）履歴置換(!$など)はなぜ動かないの？](http://fish.rubikitch.com/faq-ja/#21baa749a6b5417aeb91b3831ebd81af)

「ないんだけど、`fish`は対話的に履歴を探し出すことに秀でているから、そっちの機能を使ってね！」っという感じ。

`fish`を愛用されている方にはお馴染みなのかもしれないが、
例えば、コマンドの一部を入力して上矢印を押すと、記述したコマンドに限定して履歴を遡れたりします。（`cd`コマンド等で試してもらうと、極めて強力だということが分かるのではないでしょうか）使い慣れると`![行番号]`よりもはるかに便利だと、私は思っています。

どーーーしても`!`を使いたい場合は、`sudo !!`に対して`function`で設定する方法がissueに紹介されています。（`sudo`つけ忘れたコマンドに`sudo`をつける用途）
https://github.com/fish-shell/fish-shell/issues/288

```
function sudo
    if test "$argv" = !!
        eval command sudo $history[1]
    else
        command sudo $argv
    end
end
```
ちなみに、公式のFAQには上矢印一回して、homeボタン押してから`sudo`書けばいいじゃないか！、と書いてあります。

## 「ユーザーフレンドリ」なものに悪いものはいない？
先ほどから度々参照している、[全訳！fishシェル普及計画](http://fish.rubikitch.com/)のサイトに[設計理念](http://fish.rubikitch.com/design-document-ja/)というメニューがあり、設計原則の一つにこんなことが書かれています。
>「設定可能性」は諸悪の根源
>プログラム内のすべての設定オプションはユーザが本当に求めているものを理解するにはあまりにも馬鹿げている場所です。 また、設定オプションはプログラムとそれを実装したプログラマの双方の障害を考慮すべきです。

「根拠」では`さまざまな設定オプションをメンテナンスするのは悪夢のように困難です。`と続きます。

私が何を言いたいのかというと、上記で長々と書いた[bashでよくやる設定について](#bashでよくやる設定について)の内容は、
真っ向からこの原則に反発した形であり、
本当に`fish`で設定する必要があるのか否か、一考する余地がある、、ということです。

以下に[bashでよくやる設定について](#bashでよくやる設定について)で調べた内容について、個人的に思う「設定しなくていい理由」を書いてみました。

**○ コマンド履歴の保存件数を増やす**
（設定する方法すらありませんが・・）デフォルトで256k個もの件数が保存でき、また「重複するコマンドの履歴は保存されない」デフォルトの挙動で保存される履歴も限られている中で、これ以上件数を増やす設定は必要でしょうか？

**○ historyに日時を付与する**
元々、`-t / --show-time`のオプションで、日時の表示・非表示が自在に変更できる状況で、`alias`に設定する必要はどこまであるでしょうか？

**○ 特定のコマンドの履歴を保存しない**
無理に設定しなくてもサブコマンドの`delete`を使えば、必要のない履歴はすぐ消すことができます。また、よく必要ないと目される決まったコマンドは「重複するコマンドの履歴は保存されない」の影響で、1つしか登録されない場合が多いはずです。（一つでも登録されるのが気持ち悪いという場合は必要かもしれません）

**○ history expansion について**
慣れたら`fish`の補完の方が便利だと思います。設定してしまうと、`fish`の良さを蔑ろにしてしまう節があります。

原則を鑑みると、そもそも、（私自身そうでしたが）**使いやすくするために、設定ファイルを編集しないといけない！！**という発想自体が`fish`においては間違っていると言えそうです。
少なくとも`bash`で設定していたからと`fish`でも設定すべきかどうかは、甚だ疑問です。（耳が痛い・・・）

「ユーザフレンドリ」な`fish`は、**ガチガチに自分好みに使いたい！！**という人には、もしかすると`悪`にもなり得るのかもしれません。

`fish`は「いいぞ」ユーザフレンドリは「いいぞ」という記事が多いですが、`history`の使い方を通して、場合によっては不便な一面もあるのかなぁ、という参考資料になってくれたら幸いに思います。（`fish`に乗り換えようかと検討されている方の一助になれば・・・）

## 何かしら設定する方法
語弊がありそうなので蛇足を書きますが、もちろん、何も設定してはいけない、というわけではないと思います。
`「他のシェルほど自分好みに設定できないところがあるけど、我慢してね。一応デフォルトで使いやすくしているつもりだよ」`くらいだと思います。
何か設定するときは、
[fish shellが結構良かった話](https://qiita.com/hennin/items/33758226a0de8c963ddf)でも紹介されている。

```
fish_config
```
を使うと、ブラウザでグラフィカルに設定をいじることができます。

## 最後に

サブコマンドの`search`や`delete`は便利なので、知らなかった人は覚えておいて損はないと思います。
また、`fish`で何かしら「設定しよう」と思ったときは、まずは`fish`がデフォルトでどんな挙動をしているのか調べるのがおすすめです。（調べてみると、今回のようにそれなりに考えられた挙動だったりします）

`fish`について批判的な文も書きましたが、最終的には**とても使いやすい!!**と思っています。（特に補完機能）
なんだかんだ、これからも使っていく所存です！！


## 参考
`fish`について
[公式サイト](http://fishshell.com/)
→ 特に [公式サイトのhistoryのページ](http://fishshell.com/docs/current/commands.html#history)
[全訳！fishシェル普及計画](http://fish.rubikitch.com/)
→特に [全訳！fishシェル普及計画のhistoryのページ](http://fish.rubikitch.com/history/)
[fish-shellのGithubのページ](https://github.com/fish-shell/fish-shell)

`bash`の`history`について
[コマンド履歴を表示するhistoryコマンドを詳しく！【Linuxコマンド集】](https://eng-entrance.com/linux-command-history#history-2)
[コマンド履歴の達人を目指してみる](https://zariganitosh.hatenablog.jp/entry/20131028/history_master)