fuzzy-finderでディレクトリ探しながらcd

ターミナル操作の8割はcdとlsコマンドで操作するとかいう記事をどっかで見かけたような見かけないような。
ディレクトリ移動するだけなのに、パス名を思い出すだけなのにそんなに作業時間かけたくないですよね？

bashでは`cd`<kbd>tab</kbd>でカレントディレクトリ一覧が表示されたり、パスの途中まで打ち込んでいれば残りを補完してくれます。
zshやfishではもっと強力な補完機能がついてますが、ここでは語りません。

cdするときに3階層も4階層も下りたいけど、途中の1階層、2階層の名前が思い出せない、なんてこと、私はよくあります。


# findでcd先のディレクトリ絞り込む

どうするか？`find`コマンドや`fd`コマンド使えば、正規表現使ってマッチしたパスのフルパスを表示してくれます。

```shell-session
$ find . -name "*python*" -type d
```

`-type` オプションを指定すると検索をディレクトリのみに絞ってくれます。
しかし、検索語によっては結果が100件も200件も出てくるかもしれません。



# fzfで結果を絞り込む
fzf使えばfindで得られた数百件もの結果をインタラクティブに絞り込めます。絞り込んだあとReturnキー打てば、そのディレクトリのフルパスがSTDOUTに出力されます。

```shell-session
$ find . -name "*" -type d | fzf
```

最後にその絞り込んだ結果に`cd`するだけ。

```shell-session
$ cd $(find . -name "*" -type d | fzf)
```

上記コマンドは算術演算のカッコのように、先に$()内のコマンドを評価して、その結果を`cd`に渡しています。



# shellscriptにまとめる

正直こんな長ったらしいコマンドを`cd`する度に打ってたら作業効率化どころかむしろ作業時間増えます。
そこで、shellscriptにまとめてdotfilesにでもaliasやfunctionとして書いておいて、ターミナル作業中は短いコマンドで同じ動作が実現できるようにします。


```sh
# fd - cd to selected directory
fd() {
  local dir
  dir=$(find ${1:-.} -path '*/\.*' -prune \
                  -o -type d -print 2> /dev/null | fzf +m) &&
  cd "$dir"
}
```

これを.bashrcにでも記述して`fd`と打てば、カレントディレクトリ以下を検索してすべてのパスからfuzzy-findで絞り込みができます。
> `fd`はすでにコマンドとして普及している[^1]ので、この関数名はよくないと思います。次に説明するリポジトリが元々こういう関数名だったので残してあります。オプションの項で関数名変えます。

```sh
# fda - including hidden directories
fda() {
  local dir
  dir=$(find ${1:-.} -type d 2> /dev/null | fzf +m) && cd "$dir"
}
```

隠しディレクトリ`.config`のようなディレクトリ以下を探したいときは`fda`関数を使います。
fdでも同様に、引数にパスを入れればその下を検索してくれますし、コマンドのみであればカレントディレクトリ下を検索します。


# オプション追加

せっかくなのでいろんな`cd`を一つのshellscriptにまとめました。それが[atweiden/fzf-extras](https://github.com/atweiden/fzf-extras)のfzf-extra.shです。正確には、この中の一部をコミットしました。

```sh
zd() {
    usage() {
        echo "usage: zd [OPTIONS]"
        echo "\tzd: cd to selected options below"
        echo "OPTIONS:"
        echo "\t-d [path]: Directory (default)"
        echo "\t-a [path]: Directory included hidden"
        echo "\t-r [path]: Parent directory"
        echo "\t-s [query]: Directory from stack"
        echo "\t-f [query]: Directory of the selected file"
        echo "\t-z [query]: Frecency directory"
        echo "\t-h: Print this usage"
    }

    # No arg
    if [ ! $1 ]; then
        _fd
    # Args is '..' or '-' or [path]
    elif [ $1 = '..' ]; then
        shift; _fdr $1
    elif [ $1 = '-' ]; then
        shift; _fst "$*"
    elif [ ${1:0:1} != '-' ]; then  # first string is not -
        _fd $(realpath $1)
    # Args is start from '-'
    else
        while getopts darfszh OPT; do
            case $OPT in
                d) shift; _fd  $1; return 0;;
                a) shift; _fda $1; return 0;;
                r) shift; _fdr $1; return 0;;
                s) shift; _fst "$*"; return 0;;
                f) shift; _cdf "$*"; return 0;;
                z) shift; _zz  "$*"; return 0;;
                h) usage; return 0;;
                *) usage; return 1;;
            esac
        done
    fi
}
```


例えば上で説明した`fd`は`zd`で呼び出せます。隠しディレクトリも含めて探すなら`zd -a`のように`-a`オプションをつけます。過去に行ったことのあるディレクトリに飛びたいなら`zd -z`のように`-z`オプションをつけます。
一覧を以下に示します。

| zd オプション | 呼出関数 | シンタックスシュガー | 機能 |
|---------------|----------|----------------------|------|
| zd -d         |   _fd    | zd       | カレントディレクトリを再帰的に検索 |
| zd -a         |  _fda    | なし     | カレントディレクトリを隠しディレクトリ含めて再帰的に検索 |
| zd -s         |  _fst    | zd -     | ディレクトリスタック[^2]を検索 |
| zd -r         |  _fdr    | zd ..    | 親ディレクトリを再帰的に検索 |
| zd -f         |  _cdf    | なし     | ファイルを検索してそのファイルがあるディレクトリにcdする |
| zd -z         |  _zz     | zz       | 行ったことのあるディレクトリに移動。fasd -dをfzfで絞り込む |
| zd -h         |  usage   | なし     | helpメッセージを表示 |


fzfを使ったshellscriptが集まるこちらのリポ[atweiden/fzf-extras](https://github.com/atweiden/fzf-extras)の[fzf-extras.sh](https://github.com/atweiden/fzf-extras/blob/master/fzf-extras.sh)にプルリク送りました。
…がコミットの仕方がまずかったのか、うまいこといかなかったみたいで僕の名前はコミッターに出てきません。残念


![Screenshot from 2019-12-11 21-41-04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113494/c1c1ce09-747f-9285-41b0-b45c08449f84.png)


# 動作
動作は以下のgifで確認してください。

## zd
![Peek 2019-12-11 21-48.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113494/a9130a90-aa3f-ca58-27c0-41d02914ac60.gif)

Dropbox下のpythonディレクトリを探していますが、間にProgramディレクトリが有ります。
cdならProgramディレクトリを思い出さないとpythonディレクトリにたどり着けませんが、zdならProgramディレクトリを思い出せなくてもpythonディレクトリを見つけることができます。

![Peek 2019-12-11 23-09.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113494/e68d47a5-060e-45a0-abd6-6ceecbcf886e.gif)

カレントディレクトリに限らず、引数にディレクトリを指定すればその下のみ検索してくれます。

## zd -a

![Peek 2019-12-11 22-02.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113494/c68e6df6-48ac-d4f1-9145-645fb7d4e6ec.gif)

zdでDropbox下の.condaディレクトリを探そうとしましたが、隠しディレクトリのため表示されません。隠しディレクトリに移動するには`zd -a`のように`-a`オプションを使います。
オプション`-a`はallの頭文字です。

## zd -s (zd -)
![Peek 2019-12-11 22-11.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113494/95209e02-a7e1-263e-bbdf-587322ac6a75.gif)

`zd -s`または`zd -`で現在セッションで過去に行ったことのあるディレクトリに`cd`できます。
`cd -`で一つ前のディレクトリに戻る機能と似ているので`zd -`というシュガーシンタックスを用意しています。

## zd -r (zd ..)

![Peek 2019-12-11 22-17.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113494/6d39e158-de2d-b76a-ac88-86a91c257d06.gif)

`/home/u1and0/Dropbox/Program/Dockerfiles/composeset`というディレクトリから上の階層をfuzzy-finderで検索して、何段飛ばしでも上にいけます。
<kbd>C-j</kbd>と<kbd>C-k</kbd>で上下移動できます。

`cd ..`で親ディレクトリに飛ぶことから、`zd ..`というシュガーシンタックスを用意しています。


## zd -f
![Peek 2019-12-11 22-23.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113494/290600ac-6e05-ad19-8509-7629ba5db51f.gif)

`zd -f`でカレントディレクトリ下にあるファイルを絞り込み検索して、Returnしたファイルのあるディレクトリに`cd`します。
gifではcsvファイルの置いてあるディレクトリ一覧を表示して適当なパスを選んで`cd`しています。適当なサンプルファイルを探すときに重宝しています。
オプション`-f`はfileの頭文字です。

## zd -z (zz)
`zd -z`または`zz`使うときは[clvv/fasd](https://github.com/clvv/fasd)が必要です。`z`コマンドで表示されたディレクトリをインクリメンタルに使いたいことがそもそものモチベーションでした。

![Peek 2019-12-11 22-55.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113494/43748514-8f79-0cb5-c16d-d83c99910c93.gif)

`fasd -d`(または`d`)でスコアリングされたfrecencyディレクトリ(頻繁にfrequency + 最近recently)を表示しています。
これを`zd -z`または`zz`でfuzzy-findします。
fasdをインストールすると、これまで行ったディレクトリ(と開いたファイル)すべてが~/.fasdに保存されますので、`zd -s`と異なり、現在セッションだけでなく、これまでアクセスしたことのあるすべてのディレクトリを検索対象にできます。



[^1]: [sharkdp/fd](https://github.com/sharkdp/fd)
[^2]: 現在セッションで過去に居たディレクトリ