zshの補完

# 1.初めに

Macで標準シェルが`bash`から`zsh`に変わったので、`zsh`の補完について勉強してみました。`bash`でもプログラム可能な補完ができますが、`zsh`はそれよりも高水準の機能の補完を標準で盛り込んでいます。主に**bashではできないような補完機能**について述べていきます。

・参考文献
[zshの本](https://www.amazon.co.jp/zshの本-エッセンシャルソフトウェアガイドブック-広瀬-雄二/dp/4774138649)

# 2.zshの補完システム

zshには大別して**compsys**、**compctl**という**2つの補完システム**が存在する。バージョン3.1.6より導入された新補完システムがcompsys、それよりも前から存在するcompctlという旧補完システムである。

**新補完システムであるcompsysについて今回は見ていく**。compsysで用意されている全ての補完機能を有効にするには以下のコマンドを実行する。デフォルトではcompsysは**有効ではない**ので注意。よく使うのでこのコマンドは`~/.zshrc`などに書き込んでおくのも良い。

```:~/.zshrc
autoload -U compinit
compinit
```

# 3.補完のキーについて

既にシェルを使っている人は知っているとは思うが、補完をするには`tab`を押す。このキーは`Ctrl-i`でも**同じ**なので、どちらの入力でもOK。

また、補完候補を表示するのは**行末**で`Ctrl-d`。
<img width="457" alt="スクリーンショット 2020-06-29 18.51.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/4d1e70e3-21b8-de93-9261-89005a86989b.png">
なお文字上にカーソルがあると、`Ctrl-d`は**1文字削除**を表すので注意。また補完候補が複数ある時に`tab`を連打しても補完候補が表示される。


# 4.補完機能の概観

`tab`で補完できるのは、コマンド、コマンドのオプションやサブコマンド、ファイル名、変数、ユーザ名、ホスト名、プロセス、ジョブなど非常に幅広い。これらは`csh`や`bash`でも同じように補完できるのが大半だが、`zsh`に特有のものを少し紹介する。

## 4.1 ファイル名補完

compsysのファイル名補完では、**部分マッチ**による補完が有効。予め決められた区切り記号（デフォルトでは`/`）の直前に*を自動で補って補完する。例えば`/usr/local/bin`に行きたい時に`/u/lo/b`と打って`tab`を押すだけで良い。
<img width="457" alt="スクリーンショット 2020-06-29 19.07.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/b85ab129-fb40-d907-f631-2313412479c3.png">
↓ `tab`
<img width="457" alt="スクリーンショット 2020-06-29 19.08.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/57ee18a2-4d67-6a72-b4bd-98cc40e52f17.png">

## 4.2 変数補完

`$`記号の後ろでは、変数名を補完する。
<img width="457" alt="スクリーンショット 2020-06-29 19.29.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/1b231b66-1c58-2d74-150c-38941d5f070d.png">
↓ `tab`
<img width="457" alt="スクリーンショット 2020-06-29 19.29.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/f45cf3d1-9055-b272-173e-9d085b9504d7.png">


行頭では環境変数の代入文も記入できる（その環境変数が一時的に代入された状態でそのコマンドを実行できる）という機能があるため、**行頭での変数補完**もある。
<img width="457" alt="スクリーンショット 2020-06-29 19.28.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/68d83235-0e7b-cdcd-49cb-9c0b963bad2b.png">
↓ `tab`
<img width="457" alt="スクリーンショット 2020-06-29 19.28.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/5f4b978f-f239-7e06-0cad-999d58e1ea6d.png">

## 4.3 プロセス補完

`kill`コマンドの引数位置で`tab`を押すと、プロセス一覧が表示される。
<img width="457" alt="スクリーンショット 2020-06-29 19.43.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/73954049-5eea-eb52-f3e0-6e77d7e08c75.png">
↓ `tab`
<img width="457" alt="スクリーンショット 2020-06-29 19.43.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/37a8e5a4-2924-dffc-4623-235560ecf001.png">
また、コマンド名をPID(プロセスID)に自動変換もしてくれる。
<img width="457" alt="スクリーンショット 2020-06-29 19.47.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/8209ce7c-bfa7-d1a2-bef5-2be5eed040d3.png">
↓ `tab`
<img width="457" alt="スクリーンショット 2020-06-29 19.47.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/e0279277-fa9c-8d35-c425-10f0de1b70dc.png">

## 4.4 展開機能

`tab`には補完機能だけでなく、**展開機能**もついているので注意が必要である。
具体的には
- ファイル名展開
- ヒストリ展開
- 変数展開
- ブレース展開

などがある。これらはコマンドの実行時に内部的に変換処理が行われるものだが、コマンドラインで先に`tab`を打つことで**前もって変換させる**こともできる。補完機能ではなく本筋からズレるため詳しく扱わないが、代表的なものだけ少し紹介する。

### 4.4.1 ファイル名展開

ファイル名展開を行う例としては以下。
<img width="457" alt="スクリーンショット 2020-06-29 19.58.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/e7b2dcbf-3025-9503-21ec-e0e4e146eb10.png">
↓ `tab`
<img width="457" alt="スクリーンショット 2020-06-29 19.58.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/45d8c404-e0f1-ff1c-b402-087fdb8dc64c.png">

`*`は、任意の文字列に対応して、ファイル名展開を行う。

### 4.4.2 ヒストリ展開

ヒストリ機能には`!!`というコマンドがあり、これは一つ前に実行したコマンドを表す。
<img width="457" alt="スクリーンショット 2020-06-29 19.55.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/14c1534d-47f3-e974-2d4a-fb3b190d39bb.png">
上の例では`ls --color`を再び実行している。この`!!`を実行する前に`tab`を押すことで、先に`ls --color`を表示させることができる。
<img width="457" alt="スクリーンショット 2020-06-29 19.54.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/3203222c-d295-9b7a-1f35-ec6657a54f5d.png">
↓ `tab`
<img width="457" alt="スクリーンショット 2020-06-29 19.54.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/d7a50320-fd3a-977b-f854-7ce19c67298b.png">

同じ`tab`キーを使用するので混乱しがちだが、違う機能なので注意が必要である。
それでは補完の話に戻る。

# 5.メニュー補完

`zsh`で便利な機能として、**メニュー補完**がある。これは補完候補がたくさんある時に`tab`を連打すると順番に**その候補からコマンドラインに挿入**してくれる。以下に具体例を載せる。

`ls D`と入力した状態で`tab`を押すと候補一覧が表示
<img width="457" alt="スクリーンショット 2020-06-29 18.58.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/d8c2cb1b-3751-4793-b123-fc7e3f83cd27.png">
↓　`tab`で、一つ目のDesktopが挿入
<img width="457" alt="スクリーンショット 2020-06-29 18.58.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/185b8b16-9deb-520f-3089-9f67b21bbd50.png">
↓　再び`tab`で、二つ目のDocumentsが挿入
<img width="457" alt="スクリーンショット 2020-06-29 18.59.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/b716b606-9f00-18a5-5752-049edde4f15a.png">
↓　再び`tab`で、三つ目のDownloadsが挿入
<img width="457" alt="スクリーンショット 2020-06-29 18.58.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/35552281-1f78-f7de-1a20-50255e75a17f.png">
↓　再び`tab`で、一つ目のDesktopが挿入
<img width="457" alt="スクリーンショット 2020-06-29 18.58.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/185b8b16-9deb-520f-3089-9f67b21bbd50.png">
以下サイクルになる。これも`bash`には標準で入っていない機能である。

# 6.補完スタイル

ここからはより細かく補完を制御する話になる。少し込み入っているので、初見の方は特に自分で実際にやってみることをお勧めする。

compsysではシェルオプションによりもきめ細かい設定ができる**スタイル**によって挙動を制御する。
**スタイル**とは、compsysが補完を行う時に「どのようなことをどう行うか」を指定するためのものである。これに与える値として、真偽値だけでなく文字列や条件も与えることができる。また、シェルオプションが補完を行う**全ての局面で有効**になるのに対して、スタイルによる指定はどのような状況で補完機能を呼び出したかを表す**コンテクスト**に応じて設定値を変えることができる。

あるパターン`Pattern`にマッチするコンテクストで、ある特定のスタイル`Style`の値を`Value`に設定するには`zstyle`コマンドを用いる。構文は以下。

`zstyle Pattern Style Value`

上記だけだと理解しづらいため、これから実際の例を見ていく。

・デフォルトではメニュー補完をするが、`rm`の引数の時にはメニュー補完をしない。

```
zstyle ':completion:*' menu true
zstyle ':completion:*:rm:*' menu false
```

・`less`と`cd`の引数入力時、小文字で打った文字列で補完した時に、小文字大文字両方のファイル名にマッチさせる。

```
zstyle ':completion:*:(cd|less):*' matcher 'm:{a-z}={A-Z}'
```

`zstyle`の第一引数に与えているのが**コンテクスト**で、実際の値は6つのフィールドをコロンで区切って並べたものになる。


`completion:Func:Completer:Cmd-or-Context:Arg:Tag`

- `completion`は、補完システムで使うことを意味する固定文字列
- `Func`は、補完システムではなく名前付きウィジェットから呼ばれた場合の関数名（多くの場合空）
- `Completer`は、コンプリーター（補完ならcomplete、展開ならexpandなど）
- `Cmd-or-Context`は、保管中のコマンド・サブコマンド・その他文脈を表すシンボル
- `Arg`は、どの位置の引数を補完しているかを表すシンボル
- `Tag`は、どういう種類のものを補完しているかを表すシンボル

`rm`のメニュー補完の例では、`completion:*:rm:*`のように4つしか指定していないが、これは`*`が複数のフィールドにまたがっているだけである。ちゃんと書くなら`completion:*:*:rm:*:*`となる。

初めのうちは各フィールドの意味を詳細に知る必要はなく、とりあえず`completion:*`あるいは`completion:*:コマンド名:*`で十分である。本記事では`Func`や`Arg`、`Tag`については詳しく扱わない。本記事を読んで気になる方は、最初に紹介した参考文献をご覧いただきたい。

## 6.1 代表的なスタイル

補完スタイルの変更は、「どんな状況で」「何を」「どのように」変えるかを組み合わせて指定する。先ほどの例であったような`menu`スタイルは「メニュー補完の挙動」、`matcher`スタイルは「マッチしたとみなす文字集合列」を制御するスタイルである。

スタイルは非常にたくさんあり、バージョン5.0.7現在で111ものスタイルがある。
(バージョン5.9現在で170あることを確認)
<img width="488" alt="スクリーンショット 2020-06-29 20.37.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/ecd1c1aa-296d-cee1-4393-7380d35b20ba.png">
今回は適用用途の広い、代表的なスタイルについて示す。

### 6.1.1 補完提示方式の変更(menuスタイル)

`menu`スタイルは、メニュー補完に加えて**メニュー選択**も設定できる。これは候補一覧を**カーソルで移動して選べる方式**である。メニュー補完を有効化するtrueに加えて、メニュー補完の形式をメニュー選択にする`select`も追加文字列として指定できる。

```
zstyle ':completion:*:setopt:*' menu true select
```

この状態でsetopt autoと入力してから`tab`を打つと、メニュー選択になり、矢印キーが使えるようになる。
<img width="538" alt="スクリーンショット 2020-06-29 20.44.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/e60ae283-af58-6117-e67c-a56039784d35.png">

### 6.1.2 一覧の表示属性設定(list-colorスタイル)

`list-color`スタイルを用いれば、候補一覧を色や文字装飾付きで区別して表示できる。
例えば「ディレクトリは下線付きで、実行ファイルは黄色で、拡張子が.cのファイルは紫で表示」なら以下。

```
zstyle ':completion:*:default' list-colors di=4 ex=33 '=*.c=35'
```
<img width="428" alt="スクリーンショット 2020-06-29 22.54.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/5aedbda2-8981-313d-5106-0c2fa777fecf.png">

設定値は「種別＝表示属性」の形式を列挙する。種別には以下のようなものがある。

| 種別      | 意味                    |
|:---------|:------------------------|
| fi       | 通常ファイル              |
| di       | ディレクトリ              |
| ex       | 実行ファイル              |
| ln       | リンクファイル            |
| =パターン　| 「パターン」にマッチする候補 |


| 表示属性（XYという数字）| Y＝0 | Y＝1 | Y＝2 | Y＝3 | Y＝4 | Y＝5 | Y＝6 | Y＝7 |
|:-------------------|:-----|:-----|:-----|:----|:----|:----|:----|:-----|
| Xが空で属性設定       | 標準 | 太字 | 　　 | 　　  | 下線 | 点滅 | 　　 | 反転 |
| X=1で属性解除        | 　　 | 太字 | 　　 | 　　  | 下線 | 点滅 | 　　 | 反転 |
| X=3で文字設定        | 黒　 | 赤　 | 緑　 | 黄　  | 青　 | 紫　 | 水色 | 白 　|
| X=4で背景設定        | 黒　 | 赤　 | 緑　 | 黄　  | 青　 | 紫　 | 水色 | 白　 |


`list-colors`にスタイルに空文字列を設定すると、GNU lsと同様の色分けになる。

### 6.1.3 マッチ仕様の調整(matcherスタイル)

補完時にはマッチするものを補う。この「マッチする」という定義自体を変えることができる。

例えばfooまで打って補完すると、fooという3文字が先頭から完全に一致するものだけが補完候補として残るが、これを「大文字と小文字を区別しないで一致とする」というように変更できる。以下これを**マッチ仕様**という。

マッチ仕様の変更は、全域的な設定は`matcher-list`スタイルで行い、ある特定のタグだけに適用させたい時には`matcher`スタイルで行う。標準的な補完動作を`matcher-list`に定義しておき、特定のコマンドの補完時だけより柔軟な変更したい、というような時には、そのコマンド名を含むコンテクストに対する`matcher`スタイルの値を定義する。`matcher`スタイルに設定できる値は、`matcher-list`スタイルに適用できる値と同じである。

入力した単語の全ての文字を別のものに変えてマッチ試行させるには、

`m:Lpattern=Tpattern`
`M:Lpattern=Tpattern`

という書式を使う。これはコマンドライン上の`Lpattern`にマッチする文字列を`Tpattern`に置き換えたものでマッチングを行うことを指示する。ファイル名のように大文字と小文字が違うとエラーになるものは`m`を、シェルオプションのように大文字と小文字が同じように解釈されるものの補完では`M`を使う。

例えば

```
zstyle ':completion:*' matcher-list 'm:to=2'
```

のように定義すれば元の単語のtoを2に置き換えてくれるので、以下のようにpdftoだけでなくpdf2も補完候補に含められる。

<img width="537" alt="スクリーンショット 2020-06-29 22.42.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/cbe2fa8a-ca08-8775-6115-7278f9183092.png">

### 6.1.4 ホスト名・ユーザ名の候補(hostsスタイル、usersスタイル)

補完対象の単語の種類を表すのがタグであるが、`ssh`や`ping`はどれも引数にホスト名を要求するように、別のコマンドでも同じタグを要求することがある。このような時は、このタグそのものの候補をまとめて変更することで、そのタグを要求するコマンド全ての補完候補を変えることもできる。

ホスト名の候補は`hosts`スタイル、ユーザ名の候補は`users`スタイル、ユーザ名とホスト名を組み合わせた候補は`users-hosts`スタイルで設定する。例えば以下。

```
zstyle ':completion:*:ping:*' hosts aa.example.ne.jp bb.example.com cc.example.ac.jp
```

この設定では、pingの際のホスト名の補完候補が上記3つのものになる。

```
zstyle ':completion:*:ssh:*' users-hosts user1@{host1,host2} users2@{host3,host4}
```

この設定では、sshの際にユーザ名にuser1を入れた時はhost1かhost2だけが、user2を入れた時はhost3かhost4だけが補完候補となる。

### 6.1.5 ソート(sortスタイル、file-sortスタイル)

候補一覧は通常文字コード順にソートさせるが、候補を好きな順番に登録してその順番に出して欲しい時もある。これにはファイル名以外のソートには`sort`スタイルを用いる。

```
zstyle ':completion:*:ping:*' sort false
```

ファイル名のソートには`file-sort`スタイルで設定する。例えばlarge、small、very_large、very_smallという名前のファイルがある時、通常は文字コード順に候補が表示される。
<img width="428" alt="スクリーンショット 2020-06-29 23.21.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/ec1b9e5c-0816-fe2f-12c1-f728d865e2f2.png">
これをファイルサイズ順に表示するには以下。

```
zstyle ':completion:*' file-sort size
```

これで候補一覧の順番がサイズ順になる。
<img width="428" alt="スクリーンショット 2020-06-29 23.25.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/11df7a92-2390-85b7-31ca-f246d52e073f.png">
設定値には以下のいずれかを含む文字列とする。文字列にreverseを追加すると、逆順でソートされる。

| 設定値                      | ソートの基準       |
|:---------------------------|:-----------------|
| size                       | ファイルサイズ     |
| links                      | リンクカウント     |
| modification, date, time   | ファイル修正時刻   |
| access                     | 最終アクセス時刻   |
| inode, change              | inode修正時刻　   |

# 6.2 コンプリータ

これまでに述べた補完機能は、`_complete`というコンプリータによるもので、先頭部分を入力してそれにマッチするものを補うのが主な役割である。それ以外にも、綴り間違いを修正したり、シェルの展開機能を施すコンプリータがある。

コマンドラインで補完が要求された時に利用されるコンプリータは複数登録できる。それは`completer`スタイルで指定する。

```
zstyle ':completion:*' completer Completer1 Completer2 ...
```

このように設定すると、先頭に書かれたものから順次呼び出され、どれかのコンプリータでマッチが見つかると、以後のものは呼ばれない。

### 6.2.1 近似補完(`_approximate`)
`_approximate`コンプリータは近似マッチと同じ間違い修正をした上で補完する。このコンプリータを使う場合は、通常以下のように`_complete`コンプリータの後で稼働するように設定する。

```
zstyle ':completion:*' completer _complete _approximate
```

これにより、`_complete`による通常補完でマッチするものが探せなかった時に限り近似補完をする。この近似マッチの精度を制御するスタイルは`max-errors`スタイルで、間違いの許容数を指定する。デフォルトでは以下のように許容数は2である。

```
zstyle ':completion:*:approximate' max-errors 2 NUMERIC
```

ここでいう間違いとは「文字違い（hogeがhage）」「順番違い（emacsがemasc）」「文字抜け（magickがmagic）」「文字超過（magicがmagick）」のことである。

この状態で試しにzshをzhsと打ち間違えて`Ctrl-d`を押すと以下のようになる。
<img width="428" alt="スクリーンショット 2020-06-29 23.50.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/19f36257-2c20-d174-6863-ac8c44d3135c.png">

### 6.2.2 綴り修正(`_correct`)

`_approximate`が既に入力した部分文字列を修正してからさらに補完するのに対し、入力を終えたとみなせる単語の綴り修正のみを行うのが`_correct`コンプリータである。

```
zstyle ':completion:*' completer _complete _correct
```

これもzshをzhsと打ち間違えてみると以下のようになる。
<img width="428" alt="スクリーンショット 2020-06-29 23.53.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/ad649959-cbcf-1ca1-ac0a-009101b3c45b.png">

### 6.2.3 単語途中の補完(`_prefix`)

例えば/usr/share/manを入力しようとし、/usr/maまで打ってからshareディレクトリに気付いて入力し直すとする。

この時以下のような状況になるが、この位置で補完してもパス区切りをまたいだマッチングは起きないので、shaの部分がshareに補完されることはない。
<img width="428" alt="スクリーンショット 2020-06-30 0.14.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/395943/09cac787-f004-e21d-42d6-3fd27d1182dd.png">
この場合、カーソル以降を無視してカーソル以前の部分だけで補完するコンプリータ`_prefix`が有用である。`_prefix`は他のコンプリータと違い、それ自身が独自の補完方式を持つのではなく、単語中のカーソル以降の文字を除去した単語に対して、その時点で呼び出されるべき全てのコンプリータを呼ぶ。

```
zstyle ':completion:*' completer _complete _approximate _prefix
```

とすると、最初に`_complete`と`_approximate`が呼ばれ、それでも補完されない時に_prefixが呼ばれて、カーソル以後の文字列を無視した状態でまた`_complete`と`_approximate`が呼ばれる。

`_prefix`コンプリータが発動する時に呼ばれるコンプリータを本来のものとは違うものにもできる。その場合は例えば

```
zstyle ':completion::prefix:*' completer _complete
```

のようにコンプリータフィールドにprefixを入れたもので`completer`スタイルの指定を行う。


# 7.終わりに

zshの補完には非常にたくさんの種類があります。特にスタイルなんかはたくさんあるので、`man zshcompsys`なんかで確認しましょう。