zshにおけるディレクトリ操作について ~ 基礎  & 脱コピペ ~


## はじめに
100%趣味で書かれた記事です。

脱コピペを目標にmanを読みつつzshの理解を深めようのコーナー。
ほぼmanの翻訳みたいになったけど、ディレクトリ操作についてコメント混じりで書いていきます。

**書くこと** 

- オプション、各種コマンドについて
- 筆者の設定(zshrc)について

**書いてないこと**

- zshの導入について
- プラグインについて
- 名前付きディレクトリについて

**OS&version**

|  | Version |
| ---------------- | ------- |
| OS             | macOS Catalina  |
| zsh            | 5.8.0 |

## cd関連のオプション一覧 [:link:](http://zsh.sourceforge.net/Doc/Release/Options.html#Changing-Directories)

- `AUTO_CD`
ディレクトリ名だけを入力した場合に、そこにcdする
cd ぐらい入力したいけど `..`で親に戻るときの便利さ故に手放せない

- `AUTO_PUSHD`
  ディレクトリ移動時に、移動元をディレクトリスタックにpushする
  
- `CDABLE_VARS`
  指定したディレクトリが存在しない場合、名前付きディレクトリ展開を試みる
  補完時に 意図してない ディレクトリが表示されるので 設定には注意
  
- `CD_SILENT`
   cdした後に、カレントディレクトリを表示しない。
   ※ デフォルト(OFF)時には cd の引数が `-` だったり、ディレクトリスタック指定、cdpath内のパスを指定したとき 移動後に カレントディレクトリを表示する仕様になってるよ

- `CHASE_DOTS`
  パスに `..` が含まれる場合、物理パスの親ディレクトリとして変換する

- `CHASE_LINKS`
  パス名に含まれるすべてのシンボリックリンクを物理パスに変換する

- `PUSHD_IGNORE_DUPS`
  ディレクトリスタックに同じ名前のものをpushしない(重複除去)

- `PUSHD_MINUS`
  スタックへのアクセス時に、`'+'` と `'-'`の意味を反転させる

- `PUSHD_SILENT`
  push,またはpopdの後にディレクトリスタックを出力しない

- `PUSHD_TO_HOME`
  引数無しでpushdすると pushd $HOME となる



## cd関連の変数一覧 [:link:](http://zsh.sourceforge.net/Doc/Release/Parameters.html#Parameters-Used-By-The-Shell)
- `cdpath`
cdコマンドの検索パスを指定する。 ディレクトリを列挙する配列

- `DIRSTACKSIZE`
ディレクトリスタックの最大サイズ




##cd
zshのbuiltin-command `cd` について
特徴的なとこ：第2引数がとれるくらい。

**書式**

```
cd [ -qsLP ] [arg1 [arg2] | {+|-}N]
```

arg1 を省略したら$HOMEに移動する。
arg1 が「-」だったら移動前のディレクトリに移動する。

それ以外はこんな感じ

1. arg1が /で始めるときは普通に移動
2. カレントディレクトリ以下から argを探す
3. cdpathからargを探す
4. 名前付きディレクトリとしてargを探す
5. 上記のいずれにも当てはまらない場合は失敗

   

##### オプション一覧
| option | 説明                                                 |
| ------ | ---------------------------------------------------- |
| -q     | chpwd関数に登録されたhookを実行しない                |
| -s     | パス名にシンボリックリンクが含まれる場合は移動しない |
| -P     | シンボリックリンクを物理パスに直して移動             |
| -L     | シンボリックリンクを物理パスに直さない。             |



## pushd, dirs

移動元のディレクトリをスタック状に保存できる。 この記憶領域を`ディレクトリスタック`と呼ぶ
ここではあんまり詳細には説明しない。
`AUTO_PUSHD` オプションがあるので、`pushd`単体で使うことそうそうない気がする。
`pushd`で ディレクトリスタックにpush。`dirs` でディレクトリスタックの中身が確認できる

##### pushd

```
pushd [ -qsLP ] [arg1 [arg2] | {+|-}N]
```

オプションは `cd` と同じ

##### dirs

```
dirs [ -lpvc ]
```

- `-l` ：ディレクトリスタックを全てフルパスで表示
- `-p`：一行づつ表示
- `-v`：番号つきで表示。



## cd でディレクトリスタック参照

`cd -{+|-}N`でディレクトリスタックを参照して 移動できる。
`+ `の場合は、左(新しいほう)から、 `-`の場合は 右(古い方)から数える
`cd -<ここで補完が聞いて便利ですね>`

```
# pwd → /tmp/x
% dirs
/tmp/x /tmp/x/d /tmp/x/c /tmp/x/b /tmp/x/a

# 補完
% cd +1
1 -- /tmp/x/d
2 -- /tmp/x/c
3 -- /tmp/x/b
4 -- /tmp/x/a

# 補完2
% cd -0
0 -- /tmp/x/a
1 -- /tmp/x/b
2 -- /tmp/x/c
3 -- /tmp/x/d
```

前者のほうが直感的だと思います。
かつ `cd -` との兼ね合いで `-` のほうがわかりみが深いので `PUSHD_MINUS` オプションを指定してもいいかもしれません。


## Remembering Recent Directories [:link:](http://zsh.sourceforge.net/Doc/Release/User-Contributions.html#Recent-Directories)
真打ち登場。cdrコマンド
移動したディレクトリを履歴化、参照して移動。ということが可能になる
`ディレクトリスタック(pushd, dirs)` と似てるけど **cdrではディレクトリスタックを使用しない**
以下 `chpwd_recent_dirs` で積まれるディレクトリ履歴一覧を **ディレクトリリスト**と呼ぶことにする
デフォルトで重複除去してくれるからいいですね

##### 導入

```
autoload -Uz chpwd_recent_dirs cdr add-zsh-hook 
add-zsh-hook chpwd chpwd_recent_dirs
```

##### 書式

``` 
cdr [-l | -r | -e | -p <pattern> | <dir-num> ]
```

\<dir-num>は`ディレクトリリストの番号`。 1は直前のディレクトリとなる
引数を省略した場合は  `cdr 1` と同義
**ディレクトリリストおよびリストの順序は別ウィンドウと共通なので注意**
後述する `recent-dirs-default` を設定しているとき、 数値以外の引数の場合 `cd` と同じ動作になる

##### オプション一覧

| オプション    | 説明                                                         |
| ------------- | ------------------------------------------------------------ |
| -l            | ディレクトリリストの表示。(~置換した状態で表示される)        |
| -r            | 変数replyをディレクトリセットに設定する。                    |
| -e            | ディレクトリリストを編集する。 zleエディタぽい。             |
| -p  <pattern> | パターンにマッチする項目をディレクトリリストから削除する<br />実行後、確認を求められる。 省略したい場合は `-P` |



### 設定
`zstyle ':chpwd:*' <ここで指定するよ>`

#### recent-dirs-default

trueにすると、 コマンド引数に数値(index)以外、 または引数が複数指定された場合は `cd` コマンドとして扱う



#### recent-dirs-file

ディレクトリリストが保存されるファイル
デフォルトは `${ZDOTDIR:-$HOME}/.chpwd-recent-dirs`
深いこと考えなければデフォルト(設定無し)安定



#### recent-dirs-insert

補完候補に何を表示するかの設定。

`recent-dirs-default` がtrue かつ この項目が trueのとき、インデックスではなくディレクトリ名が補完される。
履歴に `cdr <dir-num>` の形式で残らないのでいいかも

以下、補完候補として表示されるリストとして
ディレクトリリストを`A`
カレントディレクトリ配下の移動先一覧を `B` とする

- `always` : 常にAのみ補完候補に表示する。 
- `fallback`：Aを補完候補に表示する。Aが空の場合はBを補完する
- `both`：Aの後続にBを補完候補として表示する

`always` でいい気もするが、 cdr=cd として使ってる人は `fallback` `both` がお勧めです。



#### recent-dirs-max

ファイルに保存するディレクトリの最大数。 デフォルトは `20`



#### recent-dirs-prune

履歴として積まない ディレクトリを指定する

- `parent`
ディレクトリリストから  親(祖先)を追加しない。
ホームディレクトリから `~/some` → `~/some/dirs` と移動したとする
この時 `~` と `~/some` は リストに追加されない。

- `pattern:<patten>`
追加しないパターンを指定できる。



#### recent-dirs-pushd
trueに設定すると、 cdrで pushdを使用するため ディレクトリスタックが積まれる。
ドキュメントにもあるが、 cdr ではディレクトリリストを使用するので こいつを設定するのはあんまり意味がない。



## まとめ

まとめというか、ディレクトリ操作に関する.zshrcの記述

```zsh
# -------------------
# changing directories
# -------------------

cdpath=(~)

setopt auto_cd
setopt auto_pushd
setopt pushd_minus
setopt pushd_ignore_dups

autoload -Uz chpwd_recent_dirs cdr add-zsh-hook
add-zsh-hook chpwd chpwd_recent_dirs

zstyle ':chpwd:*' recent-dirs-default true
zstyle ':chpwd:*' recent-dirs-max 500
zstyle ':chpwd:*' recent-dirs-file \
  $HOME/.cache/shell/.chpwd-recent-dirs-${TTY##*/} +
zstyle ':chpwd:*' recent-dirs-prune 'parent'
zstyle ':completion:*' recent-dirs-insert always

```

#### 補足

`chpwd_recent_dirs` に合わせて`ディレクトリスタック`も使用する設定にしている。

・過去移動したことあるディレクトリへの移動 →`cdr` 
・最近移動したディレクトリへの移動 → `cd -`でディレクトリ補完

という感じで用途を切り分けてます。前者に関しては筆者は + fzfで絞り込みしてます。

```zsh
zstyle ':chpwd:*' recent-dirs-file \
  $HOME/.cache/shell/.chpwd-recent-dirs-${TTY##*/} +
```

この部分はmanにも記載のある設定です。端末毎にディレクトリリストを分けることが可能です。

## 参考

[zsh: The Z Shell Manual](http://zsh.sourceforge.net/Doc/Release/index.html#Top)