[fish-shell] ご注文はcdしたら自動でlsですか？

fish-shell始めて2日目です。
いきなり躓いたので、ちょっと書いとこうと。

fishさん、基本的にはaliasさえ設定すれば、他は何もしなくてもいいぐらい出来のいいシェルっぽいです。
あまり深くは知らないんですけど。。。
でも、一点だけアレだったのでスクリプト書きました。

`cd`したら`ls`したいですよね。
あれ？もう`cd`したら`ls`する手癖が付いてる？
そういうのは、自動化しましょ。ね？

で、`alias cd = "my_cd; and ls"`ってやってみたものの何故か移動してくれない。
でもlsはちゃんと移動後のパスでやってくれてる。
なんでだーーー！！！ってなったんですが、関数作ってやればいいんですね。
気付くまで結構時間かかりました(´･_･`)

```shell-session:config.fish
function cd
    if test (count $argv) -eq 0
        return 0
    else if test (count $argv) -gt 1
        printf "%s\n" (_ "Too many args for cd command")
        return 1
    end
    # Avoid set completions.
    set -l previous $PWD

    if test "$argv" = "-"
        if test "$__fish_cd_direction" = "next"
            nextd
        else
            prevd
        end
        return $status
    end
    builtin cd $argv
    set -l cd_status $status
    # Log history
    if test $cd_status -eq 0 -a "$PWD" != "$previous"
        set -q dirprev[$MAX_DIR_HIST]
        and set -e dirprev[1]
        set -g dirprev $dirprev $previous
        set -e dirnext
        set -g __fish_cd_direction prev
    end

    if test $cd_status -ne 0
        return 1
    end
    ls
    return $status
end
```

bashだとbuiltinコマンドは、`\cd`って書いてたんですが、
`builtin cd`だと見直した時に分かり易いですね。
さすがfishさん

追加でこんな仕様にしてます。
1. 引数が0の時にホームディレクトリに行っちゃうのが嫌だったので、なにもせず正常終了する。
2. 存在しないディレクトリを指定してcdに失敗したら、lsは実行せず異常終了する。
移動に失敗したのに、lsされてもねぇ？
他の長い処理はデフォルト設定のままですね。

## 以下、追記

かれこれ「やっぱりBashやで」ってなってBash使ってたんですけど、Macに移行する際にFishに戻ってきました。
というのも、MacOSのBashはライセンスの関係上更新できないようでかなり古くて、かと言って上書きも怖いなーってなったからです。
で、ちょっとcdコマンドを読んでて、「あー昔はよく分からなかったけど、こんなことしてるんだ」ってのがわかったので追記。

> Bashのライセンスが非常に強烈で超絶簡単に説明すると「Bashを使ったら、リンクしてるコードもフリーソフトさせろ」みたいになってるようです。MacOS自体をフリー化させる訳にはいかないAppleは新しいBashを採用できないんですね。
> GNUにも言い分はあるんでしょうが、営利企業の事情を完全無視してるちょっと不便なライセンスですね。

### prevdを使わない理由

```fish
# Avoid set completions.
set -l previous $PWD
```

ここなんですけど、Bashでいう$OLDPWDを設定している箇所です。
Fishには過去の移動履歴を管理する`prevd`コマンドと環境変数`dirprev`がいるんですけど、なんでそれを使わないのかなーって思ってました。
自分で書き換えてうまく動かなかったことでわかったんですけど、`dirprev`に代入する処理は後段の処理がになってるんですけど、そこで初めて代入されます。
つまり、Fish shell起動直後の**1回目の`cd`コマンド**の段階では`prevd`は動いてないんですよ。

ということで、`prevd`が使えないので**cdする前にカレントパスを保存する**という方法を使ってるんですね

### ロギング機能

```fish
# Log history
if test $cd_status -eq 0 -a "$PWD" != "$previous"
   set -q dirprev[$MAX_DIR_HIST]
   and set -e dirprev[1]
   set -g dirprev $dirprev $previous
   set -e dirnext
   set -g __fish_cd_direction prev
end
```

さっきの見出しで言った移動履歴機能です。
ちょっと長いので箇条書きで書いていきます。

1. `if test $cd_status -eq 0 -a "$PWD" != "$previous"`  
   `cd`コマンドの実行結果が成功であることを判定してるのは分かると思いますが、
   今の「カレントディレクトリ」と「さっき居たディレクトリ」が同じかも判定してます。  
   同じだったら動いてないと判断して履歴に残さないようになってます。
2. `set -q dirprev[$MAX_DIR_HIST]`  
   履歴管理変数が存在することを確認します。
3. `set -e dirprev[1]`  
   履歴管理変数の先頭を削除します。  
   Fishは配列が１始まりであることに注意しましょう。
4. `set -g dirprev $dirprev $previous`  
   履歴管理変数に今回の結果を保存します。
5. `set -e dirnext`  
   新しい場所に移動したため「次」は持ってません。  
   `dirnext`を削除します。
6. `set -g __fish_cd_direction prev`  
   これもちょっとよく分からないんですけど、次のコードで`cd -`の挙動を制御してます。  
   `cd -`で「前に戻る」のか「先に行く（前の前に戻る）」のかを制御するために方向を制御していそうです。  

   ```fish
    if test "$argv" = "-"
        if test "$__fish_cd_direction" = "next"
            nextd
        else
            prevd
        end
        return $status
    end
    ```