シェルスクリプトの type, command, which 等の違いと注意点

## はじめに

この記事は `type`, `command`, `which` 等を用途で使い分けたい人のための記事です。関連がある `hash`、`whence`、`where` や紛らわしい `whereis`、`whatis`、`what` についても解説しています。これらのコマンドは、コマンドの存在チェックでよく使われますが、本来の用途というものがあるわけでその違いと実際のシェルでの実装の注意点を解説しています。速度に関しては「[Bashでコマンドの存在チェックはwhichよりhashの方が良いかも→いやtypeが最強→command -vも](https://qiita.com/kawaz/items/1b61ee2dd4d1acc7cc94)」ですでに記事があるので特に調べませんが、個人的には「シェルビルトインコマンドの方が速い」という考え方だけで十分だと思っています。

補足 この記事の Bourne シェル とは Solaris 10 の `/bin/sh` のことです。その他の（商用）UNIX の Bourne シェルでは検証していません。また POSIX シェルとは dash, busybox ash, bash, ksh88, ksh93, mksh, posh, yash, zsh のことでこれらのシェルで確認しています。バージョン番号が明記されてないものはすべてのバージョンに当てはまると考えてください。

## コマンドの存在チェックをしたい場合

各コマンドの解説が目的なので、記事の趣旨とは異なりますが「（POSIX シェルで）コマンドの存在チェックに何を使うべきか？」の答えが知りたい人のために書いておくと**ほとんどの POSIX シェルで使える `type` または `command -v (-V)`を使うべき**で `hash`、`whence`、`which`、`where` は使ってはいけません。例外は [posh](https://salsa.debian.org/clint/posh) に対応する場合で `type`、`command -v (-V)`、`hash` が実装されてないので `which` コマンドを使うか自分で実装する必要があります。`type` か `command -v (-V)` かはどちらでも良いですが、私は短い `type` の方を使っています。詳細は記事内容を参照してください。

## 外部コマンドのパスを知りたい場合

POSIX 準拠の方法で外部コマンドのパスを調べる場合、一般的には `command -v` を使うというのが答えですが、同名のシェルビルトインコマンド、シェル関数、エイリアスがある場合は、外部コマンド版があったとしてもそのパスを取得することはできません。単にコマンドがどのパスにあるかを**全て**見たいだけなら `which -a` がおすすめです。ただし zsh ではシェルビルトインコマンド等が含まれます。

どうあっても（`PATH` を検索して最初に見つけた）外部コマンドのパスが知りたいということはあまりないと思うのですが、実はこれを知るための確実で可搬性がある簡単な方法はありません。以下がその理由です。

- `type`
  - 出力形式は POSIX で規定されておらずシェル毎に異なります。
  - 同名のシェルビルトインコマンド、シェル関数、エイリアスがある場合は外部コマンドのパスは出力されません。
- `command`
  - `-V` の出力形式は POSIX で規定されておらずシェル毎に異なります。（実際のシェルでは殆どの場合 `type` と同じ内容です。）
  - `-v` は同名のシェルビルトインコマンド、シェル関数、エイリアスがある場合は外部コマンドのパスは出力されません。
  - `-p` を `-v` と併用した場合でもシェルビルトインコマンドが優先されコマンド名のみが出力され外部コマンドのパスは出力されません。
- `which`
  - POSIX で規定されていないコマンドで、インストールされていない環境もあります。
  - zsh ではシェルビルトインであり、同名のシェルビルトインコマンド、シェル関数、エイリアスがある場合は外部コマンドのパスは出力されません。

（外部コマンド版の）`which` コマンドがインストールされている環境が前提であれば `env which` (または `commnad -p which`）とすることで `PATH` から最初に見つけた外部コマンドのパスを取得することができますが、`which` コマンドが入ってない場合でも確実に外部コマンドのパスを知りたい場合は自分で実装する必要があります（[`which`](#which) 参照）。

## シェルビルトインコマンドと外部コマンドの違い

コマンドはシェルに内蔵されている場合（シェルビルトインコマンド）とシェル以外で実装されているコマンド（外部コマンド）の２種類があります。この２つの実装の根本的な違いを理解しておくと、この記事の内容が理解しやすいため説明します。シェルビルトインコマンドはシェルによって実装されているためシェルの機能を認識することができます。シェルの機能というのは、シェルビルトインコマンド、シェル関数、エイリアス、シェルのキーワード（予約語）のことでシェルビルトインコマンドであればこれらを認識することができます。しかし外部コマンドの場合はシェルから起動する別のプログラムであるためシェルの機能を認識することはできません。

この記事の内容を検証する場合、外部コマンド版の `type`、`command`、`hash` 等の存在に気をつけてください。これらはPOSIX に準拠した動作をするにはシェルビルトインでなければ実現不可能なのですが、それでも外部コマンド版が存在することがあります（macOS 等）。外部コマンドが存在することによる勘違いの一例をあげると、macOS の tcsh で `type type` を実行するとシェルビルトインコマンドであると出力されます。しかしながら `type` コマンドは tcsh のシェルビルトインコマンドではありません。これは外部コマンド版の `type` = macOS では `/bin/sh` (bash) で実行されるシェルスクリプトを実行した結果が出力されているからです。

各コマンドの実装状況の概要を以下に示します。

| コマンド | POSIX                          | ビルトインされているシェル                 | 外部コマンド版       |
| -------- | ------------------------------ | ------------------------------------------ | -------------------- |
| type     | YES (XSI)                      | POSIX シェル（posh を除く）、Bourne シェル | 一部の環境で存在する |
| command  | YES (必須、Issue6 では一部 UP) | POSIX シェル（posh は一部の機能がない）    | 一部の環境で存在する |
| hash     | YES (XSI)                      | POSIX シェル（posh を除く）                | 一部の環境で存在する |
| whence   | NO                             | ksh88、ksh93、pdksh、mksh、zsh             | 見かけない           |
| which    | NO                             | tcsh (csh)、zsh                            | 環境次第でまちまち   |
| where    | NO                             | tcsh (csh)、zsh                            | 見かけない           |

XSI と UP は POSIX の仕様上はオプションとなっているものです（下記参照）。仕様上は `command` の方が POSIX で必須となっているため `type` より良いように見えますが、ほとんどの POSIX シェルで両方ともシェルビルトインで実装されているので変わりません。例外は posh ですが posh の場合はオプション機能が実装されていません。Bourne シェルを考慮するなら `type` の方が良いと思いますが Bourne シェル は（古い商用の） UNIX で使われていたシェルで、算術式展開などが使えない等、POSIX シェルと完全な互換性がないので殆どの場合考慮する必要はないと思います。

- [XSI: X/Open System Interfaces](https://pubs.opengroup.org/onlinepubs/9699919799/help/codes.html#XSI)
- [UP: User Portability Utilities](https://pubs.opengroup.org/onlinepubs/9699919799/help/codes.html#UP)

## `which` と `where` の由来？

調べていて気づいたのですが、`which` と `where` はおそらく csh/tcsh 由来のコマンドではないかと思っています。どちらも csh/tcsh のシェルビルトインコマンドです。`where` は `which -a` 相当のコマンドです（ただし csh/tcsh では `which` に `-a` オプションはありません）。

zsh で `which` と `where` がシェルビルトインコマンドである理由は、zsh が ksh (POSIX シェル）だけでなく csh/tcsh の機能も取り込んだシェルだからでしょう。Bourne シェルと csh/tcsh が次世代の標準シェル争いをしていた古い時代では Bourne シェル では `type`、csh/tcsh では `which` を使うのがコマンドの存在チェックの定番だったのではないかと推測しています。

## 各コマンドの詳細

### type (POSIX)

コマンドの種類を表示する

指定した単語が、シェルビルトインコマンド、シェル関数、エイリアス、キーワード（予約語）、外部コマンド（、それ以外のシェル独自の種類）のどれなのかを調べるときに使います。いずれかである場合は見つかった種類を出力し終了ステータス `0` で正常終了します。出力は「未定義の形式で出力される」のでシェルによって出力形式が異なります（外部コマンドのパスが出力に含まれることは必須ではありません）。どれにも当てはまらない場合は終了ステータス 非 `0` で失敗となります。

#### 実装に関する特記事項

POSIX で規定されており POSIX シェルではシェルビルトインとして実装されています。ただし必須ではない（XSI 拡張オプション）ので実装されていないシェルがあります。Bourne シェルでもシェルビルトインで実装されています。

[POSIX (Issue 6): type](https://pubs.opengroup.org/onlinepubs/007904875/utilities/type.html)

```sh
# [XSI] X/Open System Interfaces: The functionality described is
# part of the X/Open Systems Interfaces option.
type name...
```

[POSIX (Issue 7): type](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/type.html)

```sh
# [XSI] X/Open System Interfaces: The functionality described is
# part of the X/Open Systems Interfaces option.
type name...
```

- mksh
  - `\builtin whence -v` のエイリアスです。以前（R50dあたり？）は`whence -v`のエイリアスでした。
- ksh93
  - `whence -v` のエイリアスです。ksh93u+m ではシェルビルトインです。
- posh
  - `実装されていません（XSI 拡張のオプションのため？）。
- zsh
  - `whence -v` 相当です（ただしエイリアスではない）。
- macOS
  - `/usr/bin/type` が存在しますが、`/bin/sh` で実行されるシェルスクリプトなので、実際には bash で実行された結果が表示されます。
- FreeBSD
  - `/usr/bin/type` が存在しますが、`/bin/sh` で実行されるシェルスクリプトなので、実際には FreeBSD sh で実行された結果が表示されます。
- Solaris 10
  - `/usr/bin/type` が存在しますが、`/bin/ksh -p` で実行されるシェルスクリプトなので、実際には ksh88 で実行された結果が表示されます。
  - `/usr/xpg4/bin/type` には `/usr/xpg4/bin/sh -p` (ksh93u+) で実行されるシェルスクリプト版もあります。
- Solaris 11
  - `/usr/bin/type` が存在しますが、実装は `type` が実行されるようにビルドされた ksh93u+ のようです。
  - `/usr/xpg4/bin/type` には `/usr/xpg4/bin/sh -p` (ksh93u+) で実行されるシェルスクリプト版もあります。
- tcsh
  - 実装されていません。

### command (POSIX)

コマンドの実行、またはコマンドのパスや種類の表示

シェル関数の探索をバイパスし、シェルビルトインコマンドまたは外部コマンドを実行したり、コマンドのパスや種類を表示するコマンドです。`-v` オプションの出力は処理しやすいように POSIX で定義されており、`-V` は人間にとって読みやすい形式で表示されます（`type` の出力と同等）。

#### 実装に関する特記事項

POSIX で規定されており POSIX シェルではシェルビルトインとして実装されています。ただし `-v`, `-V` オプションは必須ではない（UP オプション）ので実装されていないシェルがあります。また Bourne シェルでは実装されていません。

[POSIX (Issue 6): command](https://pubs.opengroup.org/onlinepubs/007904875/utilities/command.html)

```sh
command [-p] command_name [argument ...]

# [UP] User Portability Utilities: The functionality described is optional.
command [ -v | -V ] command_name
```

Issue 6 では `-p` と `-v`, `-V` を組み合わせることができないことに注意。

[POSIX (Issue 7): command](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html)

```sh
command [-p] command_name [argument...]
command [-p][-v|-V] command_name
```

`-v` (`-V`) を指定した場合は POSIX の仕様では複数のコマンドを指定できないことに注意。複数のコマンドを指定した場合やコマンドが見つからなかった場合の終了ステータスはシェルによって異なります。

- dash
  - `command -v` または `command -V` は一つのコマンドしか調べられません。
- BusyBox ash
  - `command -v` または `command -V` は一つのコマンドしか調べられません。
- bash
  - `command -v` または `command -V` で複数のコマンドを指定した場合、少なくとも一つのコマンドが見つかった場合に終了ステータスは `0` になります（POSIX モードでも同じ）。
- ksh88
  - `-p` オプションと `-v`, `-V` を組み合わせることができません。Issue 6 の定義は最初の POSIX シェルである ksh88 の仕様に基づくものだと思われます。
  - デフォルトで `command='command '` のエイリアスが定義されています（最後の空白により `command` に続く単語もエイリアス展開が行われる）。
- ksh93
  - デフォルトで `command='command '` のエイリアスが定義されています（最後の空白により `command` に続く単語もエイリアス展開が行われる）。
  - ksh93u+m では上記のエイリアスは定義されていません。
- mksh
  - R59c-1 では `command -v` で `PATH` 環境変数の相対パスでコマンドが見つかった場合に絶対パスを出力するように修正されました。
  - R51 より前は `command -v` でシェルの予約語が検索できません（`type` でなら可能）。
- posh
  - `-v`、`-V` オプションは実装されていません（Issue 6 では UP オプションのため？）。Issue 7 では必須ですが追尾できていません。
- yash
  - `command -v` はシェルビルトインコマンドがある場合でも外部コマンドのパスを優先して出力します（下記 補足2 参照）。
- zsh
  - 5.2 系とそれより前のバージョンでは `-p` オプションと `-v`, `-V` を組み合わせることができません（Issue 6 参照、5.3のリリースは 2016-12-12）。
  - 5.3 系とそれより前のバージョンでは `\command -v` がエラーになります。
  - `command`（オプションなし）の場合、シェルビルトインコマンドは実行されず必ず外部コマンドが実行されます。
- Bourne シェル
  - 実装されていません。
- tcsh
  - 実装されていません。

補足 dash、bash、busybox ash、mksh、zsh など多くのシェルで `command -v` で `PATH` 環境変数の相対パスでコマンドが見つかった場合に相対パスで出力されてしまうという問題があります。これは[絶対パスで出力する](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html)という POSIX の仕様に準拠してないと思われます（これはもう POSIX 側を修正すべきな気がする）。

> Utilities, regular built-in utilities, command_names including a <slash> character, and any implementation-defined functions that are found using the PATH variable (as described in Command Search and Execution), shall be written as absolute pathnames.

補足2 yash では他のシェルと異なり `command -v echo` で `/bin/echo` と出力されますが、上記の POSIX の仕様より regular built-in utilities (`echo`、`test` 等)は絶対パスで出力するとあるので yash の動作が正しいと思われます。（[参考](https://unix.stackexchange.com/questions/496259/slightly-confused-about-whether-printf-in-the-yash-shell-is-a-built-in-command-o)）

#### `builtin command` について

https://please-sleep.cou929.nu/check-command-existence-on-shellscript.html より

> rvm がこういうふうにやっていた. なぜ builtin command というふうにわざわざやっているのかがよくわからない. command っていうコマンドがビルトイン以外にもあってかぶるケースがあるのかな.

少し気になったので調べた所、rvm が使用している [SM Framework](https://github.com/sm/sm)（これが何なのかは分かってない）が、[`command` コマンドをシェル関数で再定義している](https://github.com/sm/sm/blob/master/core/api/shell/command/functions
)からのようです。一応本来の `command` コマンドと互換性を持たせつつ拡張しているようですが何かしらの問題があったのでしょう。普通はわざわざ `builtin` をつける必要はありません。

### hash (POSIX 準拠)

ハッシュテーブルの内容の表示、コマンドの追加、内容の削除を行う

`PATH` 環境変数からコマンドを探索する処理は時間ががかかるため、シェルはコマンド名とパスの対応をハッシュテーブルに格納します。この処理は通常コマンド実行時に自動的に行われます。また環境変数 `PATH` に値を代入するとハッシュテーブルはクリアされます（`hash -r` 相当）これらの処理を手動で行うのが `hash` コマンドです。本来はハッシュテーブルの操作用のコマンドなので用途通りの使い方ではありませんし、**mksh と ksh ではコマンドが見つからない場合でもエラーにならないのでコマンドの存在チェックに使ってはいけません**。

引数がない場合はハッシュされたコマンド一覧が出力されますが、POSIX では「出力形式は指定されていない」とあるのでシェルによって異なります。ただし「その出力にパスが含まれている」と記載されています。

#### 実装に関する特記事項

[POSIX (Issue 6): hash](https://pubs.opengroup.org/onlinepubs/007904875/utilities/hash.html)

```sh
# [XSI] X/Open System Interfaces: The functionality described is
# part of the X/Open Systems Interfaces option.
hash [utility...]
hash -r
```

[POSIX (Issue 7): hash](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/hash.html)

```sh
hash [utility...]
hash -r
```

- ksh
  - 指定したコマンドが見つからない場合も正常終了します（終了ステータス `0`）。
- mksh
  - 指定したコマンドが見つからない場合も正常終了します（終了ステータス `0`）。
- posh
  - `実装されていません（XSI 拡張のオプションのため？）。
- tcsh
  - 実装されていません。

補足 ksh と mksh では指定したコマンドが見つからない場合も正常終了しますが、POSIX の仕様では「見つからない場合は失敗である」とは書いていないようなので POSIX の仕様には違反していないと思われます。

### which

指定したコマンドの場所を探索する

指定したコマンドの場所を探索するためのコマンドです。**POSIX では規定されておらず**デフォルトでインストールされていない環境もあります。基本的に外部コマンドとして実装されており OS によって異なる実装を使用していますが、zsh ではシェルビルトインコマンドとして実装されているので注意が必要です。具体的には macOS （デフォルトシェルが zsh に変わった）でシェルから実行した場合とシェルスクリプトから実行した場合で出力結果が異なる場合があります。外部コマンドの `which` ではシェルのキーワード（予約語）やシェル関数を見つけることができませんが、zsh のシェルビルトインコマンドでは見つけることができてしまうからです（例 zsh では `which "[["` が `shell reserved word` と表示される）。`-a` オプションは多くの環境で使えますが、Solaris 10（古い UNIX？）では実装されていません。その他のオプションは移植性が低いです。個人的な意見では**which コマンドはシェルスクリプトでは使うのはやめ、手作業でコマンドの場所を調べたいときに使うもの**と考えたほうが良いと思います。

#### 実装に関する特記事項

- Debian / Ubuntu
  - [debianutils 版の `which` コマンド][debian_which]が採用されています。
  - POSIX 準拠のシェルスクリプトで実装されています。
    - Debian 3.0 までは bash スクリプトで `type -p` 相当でした（bash の `type` には `-a` オプションがあります）。
  - `-a` オプションのみが実装されています。
- CentOS / RedHat
  - [GNU 版の `which` コマンド][gnu_which]]が採用されています。
  - v2.21 時点で `-a` オプションは実装されていますが、`-s` オプションは実装されていません。
  - その他いくつかの（風変わりな）オプションが多数実装されています。（下記参照）
  - 最小構成ではインストールされません。
- Alpine Linux / BusyBox
  - [BusyBox アプレット版の `which` コマンド][busybox_which]が採用されています。
  - v1.33.1 時点で `-a` オプションのみが実装されています（ただし usage に表示されていません）。
- macOS
  - （おそらく）FreeBSD 版の `which` コマンドが採用されています。
  - `-a` オプションと `-s` オプション（標準出力に表示しない）のみが実装されています。
  - 注意 zsh のシェルビルトインの `which` コマンドと混同しないように注意してください。
- FreeBSD
  - FreeBSD 版の `which` コマンドが採用されています。
  - `-a` オプションと `-s` オプション（標準出力に表示しない）のみが実装されています。
- NetBSD
  - NetBSD 版の `which` コマンドが採用されています。
  - `-a` オプションと `-p` オプションが実装されています。
    - `-p` オプションは usage に表示されますが、`man which` には乗っておらず詳細は不明です。
- OpenBSD
  - OpenBSD 版の `which` コマンドが採用されています。
  - `-a` オプションのみが実装されています。
- Solaris 10
  - Solaris 版の `which` コマンドが採用されています（パスは `/usr/bin/which`）。
  - 実装は csh スクリプトです。
  - **オプションはなにも実装されていません。**
  - 注意 csh のシェルビルトインの `which` コマンドと混同しないように注意してください。
- Solaris 11
  - GNU 版の `which` コマンドが採用されています（パスは `/usr/bin/which`）。
- mksh
  - シェルでは実装されていませんが [dot.mkshrc](https://github.com/MirBSD/mksh/blob/master/dot.mkshrc)でシェル関数として実装するサンプルが付属しており `man mksh` にも記載されています（`-a` オプションのみが実装されています）。
- zsh
  - シェルビルトインコマンドです。
  - `whence -c` 相当です（エイリアスではない）。
  - `whence` コマンドのオプションが使用できますが、`which` コマンドのオプションとは互換性がありません。`-a` オプションのみ `which` コマンドと同じ意味です。
- tcsh
  - シェルビルトインコマンドです。
  - オプションはなにも実装されていません。

[debian_which]: https://salsa.debian.org/debian/debianutils/-/blob/master/which
[gnu_which]: https://savannah.gnu.org/projects/which/
[busybox_which]: https://git.busybox.net/busybox/tree/debianutils/which.c

#### debianutils which の補足

Debian で現在採用されている debianutils which はシェルスクリプトで実装されています。最近のコミットを見る限り、POSIX で定義されている `type` または `command -v` を使うことを推奨し、debianutils 版の `which` コマンドは Debian 11 を最後に非推奨となるようです（おそらく別パッケージへ移行）。現在は最小構成でもインストールされていますが、Debian 12 以降は最小構成ではインストールされなくなる可能性があるのではないかと思っています。（この記事を書いているのは Debian 11 リリース前です。）

[debianutils which のライセンス](https://salsa.debian.org/debian/debianutils/-/blob/8eaf897505f5d414d8b1933f13ec61ca7c517128/debian/copyright)によると Public Domain (`which is in the public domain.`) ということなのでライセンスを気にすることなくシェルスクリプト版の実装コードとして利用することができます。これは特に `type`、`command -v (-V)`、`hash` に対応してない posh でコマンドの存在チェックをしたいときに便利でしょう。参考として macOS 等との互換性向上のために `-s` オプションが一時的に追加された時の[コミット](https://salsa.debian.org/debian/debianutils/-/blob/8eaf897505f5d414d8b1933f13ec61ca7c517128/which
)を提示します（その後、非推奨とする過程で revert されました）。

#### GNU which の詳細

外部コマンドでありながら、シェルのエイリアスやシェル関数を見つけることができるように設計されているようです。そのために `--read-functions` や `--read-alias` を使って現在のシェルの状態を標準入力から読み取って判断する仕組みを備えています。ただしそのままでは使いづらいため `man which` ではシェル関数を定義する例が記載されています。この例では `which` と同じく GNU である bash 特有の機能を使っているので bash でしかうまく動かない可能性があります。個人的にはここまでして `which` コマンドでエイリアスやシェル関数を調べたいとは思いません（`type` や `command` を使います）。過剰すぎるこのオプションが Debian が GNU which を採用しなかった理由かもしれません。

```console
Usage: which [options] [--] COMMAND [...]
Write the full path of COMMAND(s) to standard output.

  --version, -[vV] Print version and exit successfully.
  --help,          Print this help and exit successfully.
  --skip-dot       Skip directories in PATH that start with a dot.
  --skip-tilde     Skip directories in PATH that start with a tilde.
  --show-dot       Don't expand a dot to current directory in output.
  --show-tilde     Output a tilde for HOME directory for non-root.
  --tty-only       Stop processing options on the right if not on tty.
  --all, -a        Print all matches in PATH, not just the first
  --read-alias, -i Read list of aliases from stdin.
  --skip-alias     Ignore option --read-alias; don't read stdin.
  --read-functions Read shell functions from stdin.
  --skip-functions Ignore option --read-functions; don't read stdin.

Recommended use is to write the output of (alias; declare -f) to standard
input, so that which can show aliases and shell functions. See which(1) for
examples.

If the options --read-alias and/or --read-functions are specified then the
output can be a full alias or function definition, optionally followed by
the full path of each command used inside of those.

Report bugs to <which-bugs@gnu.org>.
```

```sh
alias which='alias | which -i'

which() { declare -f | which --read-functions $@ }
export -f which
```

補足 `declare` は POSIX で規定されておらず `export -f` が使えるのは bash のみ

```
EXAMPLE
  The recommended way to use this utility is by adding an alias (C shell)
  or shell function (Bourne shell) for which like the following:

  [ba]sh:

    which ()
    {
      (alias; declare -f) | /usr/bin/which --tty-only --read-alias --read-functions --show-tilde --show-dot $@
    }
    export -f which

    [t]csh:

      alias which 'alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'
```

### whence

コマンド検索を包括的にサポートしているコマンド

一部のシェルでは `command`、`type`、`which`、`where` は `whence` のエイリアス（または同等の実装）になっています。ksh88、ksh93、pdksh、mksh、zsh で実装されています。一部のシェルでしか使えないため**必要がない限り使わないほうが良い**です。以下は `man ksh` からの抜粋です。最も古い ksh88 では `-p` オプションと `-v` オプションしか存在しませんでした。

ksh88 (Solaris 10)

```
whence [ -pv ] name ...
    For each name, indicates how it would be interpreted if used as a command name.
    The -v flag produces a more verbose report.
    The -p flag does a path search for name even if name is an alias, a function, or a reserved word.
```

ks93

```
whence [ -afpv ] name ...
    For each name, indicate how it would be interpreted if used as a command name.
    The -v option produces a more verbose report.  The -f option skips the search for functions.
    The -p  option  does  a  path search for name even if name is an alias, a function, or a reserved word.
    The -p option turns off the -v option.  The -a option is similar to the -v option
    but causes all interpretations of the given name to be reported.
```

pdksh

```
whence [-pv] [name ...]
    For each name, the type of command is listed (reserved  word, built-in, alias, function, tracked
    alias or executable). If the -p option is used, a path  search  done even if name is a reserved word,
    alias, etc.  Without the -v option, whence is similar to command -v except that whence will find
    reserved words and won't print aliases as alias commands; with the -v option, whence is the same as
    command -V. Note that for whence, the -p option does not affect the search path used, as it does for
    command. If the type of one  or more of the names could not be determined, the exit status is non-zero.
```

mksh

```
whence [-pv] [name ...]
    (regular) Without the -v option, it is the same as command -v, except aliases are printed as their
    definition only.  With the -v option, it is exactly identical to command -V.  In either case, with
    the -p option the search is restricted to the (current) PATH.
```

zsh

```
whence [ -vcwfpamsS ] [ -x num ] name ...
    For each name, indicate how it would be interpreted if used as a command name.
    If name is not an alias, built-in command, external command, shell function, hashed command,  or  a
    reserved  word,  the exit status shall be non-zero, and -- if -v, -c, or -w was passed -- a message
    will be written to standard output.  (This is different from other shells that write  that  message
    to standard error.)

    whence is most useful when name is only the last path component of a command, i.e. does not include
    a `/'; in particular, pattern matching only succeeds if just the  non-directory  component  of  the
    command is passed.

    詳細なオプションの説明は省略。man を参照してください。
```

#### 実装に関する特記事項

- ksh93
  - `command -v` は `whence` 相当です。
  - `type` は `whence -v` のエイリアスです。
  - `command -V` は `whence -v` 相当です。
- mksh
  - `command -v` は `whence` 相当です（エイリアスの出力が僅かに異なる）。
  - `type` は `whence -v` (`\builtin whence -v`) のエイリアスです。
  - `command -V` は `whence -v` と全く同じです。
  - `which`（サンプルのシェル関数）は `whence -p` 相当です。
- zsh
  - `command -v` は `whence` 相当です。
  - `command -V` は `whence -v` 相当です。
  - `type` は `whence -v` 相当です。
  - `where` は `whence -ca` 相当です。
  - `which` は `whence -c` 相当です。

### where

指定したコマンドがある場所を全て探索する

`which` が `PATH` 環境変数から見つかった最初のコマンドだけを返すのに対して、`where` は `PATH` から見つかったすべてのコマンドを返します。実装されているシェルは zsh と `tcsh` (csh) だけで外部コマンド版は（私が知る限り）ありません。殆どの `which` コマンドは `-a` オプションを実装しており、これを `where` の代わりに使うことができます。そのため **`where` コマンドを使うのは tcsh を使うときぐらい**でしょう。

#### 実装に関する特記事項

- zsh
  - シェルビルトインコマンドです。
  - `whence -ca` 相当です。
- tcsh (csh)
  - シェルビルトインコマンドです。
  - オプションはなにも実装されていません。

余談ですが Windows には `where.exe` コマンドがありますが機能は全く異なります。cygwin や msys だと Windows版の `where.exe` が（拡張子なしで）実行できてしまうので注意が必要です。

### 紛らわしい名前のコマンド達

#### whereis

コマンドだけではなく man ページや ソースコードのパスも検索します。ただし macOS / BSD 系では、コマンドのパスを検索するだけの機能しか持ってないように思えます。検索パスも環境変数 `PATH` ではなく `sysctl user.cs_path` などを使うようです。開発者のための検索コマンドとして作られたのでしょうか？ ちなみに man ページのパスは `man --path コマンド` で調べることができます。

#### whatis

コマンドと簡単な説明を検索して表示するコマンドです。以下のような出力が得られます。

```console
$ whatis date
cal(1), ncal(1)          - displays a calendar and the date of Easter
date(1)                  - display or set date and time
iwidgets_datefield(n), iwidgets::datefield(n) - Create and manipulate a date field widget
gdate(1), date(1)        - print or set the system date and time
```

#### what

[`what`](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/what.html) はPOSIX で規定されていますが、これは（もはや使われていない）バージョン管理システムの SCCS 関連のコマンドの一つで、コマンドの識別文字列を表示するためのまったく関係ないコマンドです。以下のような出力が得られます。

```console
$ what /bin/ls
/bin/ls
	PROGRAM:ls  PROJECT:file_cmds-321.100.11
	 Copyright (c) 1989, 1993, 1994
	PROGRAM:ls  PROJECT:file_cmds-321.100.11
	 Copyright (c) 1989, 1993, 1994
```

実行ファイル（バイナリ or スクリプト）の中に `@(#)` で始まる文字を入れておくと、その部分をコマンドの識別文字列として表示することができます。macOS などでは `ls` コマンドなど全てではないですが今も識別文字列が埋め込まれており `what` コマンドでコマンドの情報を見ることができます。識別文字列が埋め込まれているかはコマンド次第です。これはこれで便利なので SCCS から分離してこの仕様だけ残したらよかったんじゃないかなと思わなくもないです。