「which」を使わないのはなぜですか?それでは何を使うべきですか?

「which」を使わないのはなぜですか?それでは何を使うべきですか?

実行可能ファイルへのパスを見つけたり、Unixシェルにコマンド名を入力したりすると、何が起こるのかを判断するためのさまざまなユーティリティ(、、、、、など)がwhichあります。typecommandwhencewherewhereiswhatishash

which私たちはこれを避けるべきだと言うことをよく聞きます。なぜ?代わりに何を使うべきですか?

ベストアンサー1

あなたが知りたくないと思ったことはすべて次のとおりです。

一般化する

Bourneに似たシェルスクリプトから実行可能ファイルのパス名を取得するには(いくつかの注意事項があります、以下を参照)。

ls=$(command -v ls)

特定のコマンドが存在することを確認するには、次のようにします。

if command -v given-command > /dev/null; then
  echo given-command is available
else
  echo given-command is not available
fi

Bourneのような対話型シェルのプロンプトで:

type ls

このwhichコマンドはCシェルの破壊的な遺産であり、Bourneのようなシェルに単独で置くのが最善です。

はい

この情報をスクリプトの一部として見つけることと、シェルプロンプトで対話的に見つけることには違いがあります。

シェルプロンプトで一般的なユースケースは次のとおりです。このコマンドは奇妙に動作します。正しいコマンドを使用していますか?私がこれを入力している間、一体何が起こっているのでしょうかmycmd?どんな内容なのか、もう少し詳しく調べてもいいですか?

この場合、実際にコマンドを呼び出すよりも、コマンドが呼び出されたときにシェルが何をするのかを知りたいです。

シェルスクリプトではかなり異なる傾向があります。シェルスクリプトで単にコマンドを実行したい場合は、コマンドがどこにあるのか、何かを知りたい理由はありません。一般的に知りたいのは実行可能ファイルへのパスであるため、そのファイルからより多くの情報を取得できます(たとえば、そのファイルに関連する他のファイルのパスまたはそのパス情報から実行可能ファイルの内容を読み取ることができます)。

インタラクティブな方法で次のことを知りたい場合もあります。みんなmy-cmdこのように、システム(スクリプト)で使用できるコマンドはほとんどありません。

利用可能なほとんどのツール(しばしばそのような場合)は、対話型で使用するように設計されています。

歴史

まず、少しの歴史です。

1970年代後半まで、初期のUnixシェルには機能やエイリアスはありませんでした。エイリアスは1978年頃に導入されました(最初に紹介されましたが$PATH)。cshcsh解放、1979年2BSD5月).cshrcカスタムシェルに対しても処理されます(たとえば、各シェルはcshスクリプト.cshrcのようにインタラクティブでない場合も読み取られます)。

rcBourneシェルは1979年初頭にUnix V7で初めてリリースされましたが、機能サポートははるかに後で追加されました(1984年SVR2で)、いずれの場合もいくつかのファイルがありませんでした(.profile非シェル環境設定用)それ自体)。

cshインタラクティブな使用に便利で、より多くの機能を追加するため、Bourneシェルよりも人気があります(構文はBourneシェルよりはるかに悪いです)。

3BSD(1980)ではwhichcshスクリプトcshユーザーが実行可能ファイルを識別するのを助けるために追加されたこのスクリプトwhichは、今日多くの市販のUnices(Solaris、HP / UX、AIX、またはTru64など)で見つけることができるスクリプトとほとんど変わりません。

スクリプトは、ユーザーのコマンド~/.cshrc(呼び出しを使用しない限り、すべてのスクリプトと同様)を読み取り、エイリアスリストとcsh(保持されている配列に基づいて)csh -f提供されたコマンド名を探します。$pathcsh$PATH

ここにあります:which最初のものは当時最も人気のあるシェルでした(csh90年代半ばまでまだ人気がありました)、これが本に書かれていて広く使われている主な理由です。

cshユーザーにとっても、whichcshスクリプトが必ずしも正しい情報を提供するわけではありません。~/.cshrc後でプロンプトで定義したり、source別のファイルを指定してエイリアスをインポートしたりするのではなく、で定義されたエイリアスをインポートしcsh(良い考えではありませんが)、PATH再定義することができます~/.cshrc

whichBourne シェルでコマンドを実行すると、 で定義されたエイリアスを探すことができますが、~/.cshrcエイリアスを使用せずにエイリアスがない場合cshでも正しい答えを得ることができます。

1984年になると、組み込みコマンドによりSVR2のBourneシェルに同様の機能が追加されましたtype。 (外部スクリプトではなく)組み込まれているという事実はできるシェルの内部にアクセスできるので、正しい情報(ある程度)を提供します。

初期コマンドは、コマンドが見つからない場合に失敗した終了状態を返さないという点で、typeスクリプトと同様の問題を経験しました。また、実行ファイルの場合は、代わりに同様のものを出力するのwhichとは異なり、スクリプトで使用するのは簡単ではありません。whichls is /bin/ls/bin/ls

Unixバージョン8(一般には公開されていません)のBourneシェルに組み込まれている関数のtype名前が変更され、whatis引数と印刷関数の定義も報告するように拡張されました。また、type名前が見つからないときに失敗が返されない問題を解決します。

rc、Plan9(ワンタイムUnix以降)シェル(およびakangaなどの派生製品eswhatisも使用できます。

sh80年代半ばに開発されたが、1988年以前には広く使用されていなかったKornシェル(POSIX定義の基礎となるサブセット)cshは、Bourneシェルの上に多くの機能(ラインエディタ、エイリアス...)を追加しました。これには、いくつかのオプション(同様に詳細な出力を提供し、実行可能ファイルのみを検索(別名/関数の代わりに...))を使用する独自の組み込みwhence関数()が追加されます。type-vtype-p

AT&TとBerkeleyとの著作権紛争と同時に一部フリーソフトウェアシェルの実装は1980年代後半と1990年代初頭に登場しました。 Almquistシェル(BSDのBourneシェルに代わるもの)ash(FSFスポンサー)のすべてのパブリックドメインの実装は、1989年から1991年の間に登場しました。kshpdkshbashzsh

AshはBourneシェルを置き換えることを意図していましたが、typeずっと後で(NetBSD 1.3とFreeBSD 2.3で)組み込み機能はありませんでしたhash -v。ただし、OSF / 1にはOSFまで常に0を返す組み込み機能が/bin/shありました。type/ 1 v3.x。bash何も追加されていませんが、パス( like )とレポートを印刷するオプションがwhence追加されました。-ptypetype -pwhence -p-aみんな一致するコマンド。 s -likeコマンドがtcsh組み込まれてwhich追加されました。すべて。wherebashtype -azsh

Shell fish(2005)では、コマンドがtype関数として実装されています。

同時に、cshスクリプトはwhichNetBSDから削除され(tcshに組み込まれていて他のシェルではあまり使用されていないため)、機能が追加されました(呼び出されたwhereisとき)。 OpenBSDとFreeBSDではこれもCで書かれています。whichwhereiswhich$PATHwhich$PATH

実装する

さまざまな Unices には、コマンドの数十種類の構文とwhich動作の実装があります。

tcshLinuxでは、(およびの組み込み実装に加えてzsh)いくつかの実装を見つけることができます。たとえば、最近のDebianシステムでは$PATH

busyboxコマンドがもう1つありますwhich

一つは、GNU whichおそらく最も豪華なものです。 cshスクリプトの機能をwhich別のシェルに拡張しようとします。エイリアスと機能が何であるかを知ることで、より良い回答を提供できます(一部のLinuxディストリビューションでは、これにグローバルエイリアスが設定されていると思いますbash)。

zshいくつかあります。オペレーター実行可能ファイルのパスに展開されます。= ファイル名拡張子演算子と:c履歴拡張修飾子(ここでは次に適用されます)パラメータ拡張):

$ print -r -- =ls
/bin/ls
$ cmd=ls; print -r -- $cmd:c
/bin/ls

zshzsh/parametersコマンドハッシュテーブルは、モジュールのcommands連想配列としても使用されます。

$ print -r -- $commands[ls]
/bin/ls

このユーティリティ(Unix V8 BourneシェルまたはPlan 9 /のユーティリティをwhatis除く)は、ドキュメント(greps Whatisデータベース、つまりマニュアルページの概要)にのみ使用されるため、実際には関係ありません。rces

whereis3BSD実行可能ファイル、マニュアルページ、ソースコードを同時に見つけるために使用されるのではなく、現在の環境に基づいていないで、まるで作成されたかのようにwhich同時に追加されます。繰り返しますが、これは他の要件を満たしています。Ccsh

標準の点では、POSIXはcommand -vand-Vコマンド(POSIX.2008までオプション)を指定します。 UNIX指定typeコマンド(オプションなし)それはすべてです(where、、、whichwhenceどの標準でも指定されていません)。

一部のバージョンまではtypecommand -vLinux Standard Baseの仕様ではオプションでした。これは、たとえば、いくつかの以前のバージョンposh(両方に基づいているにもかかわらずpdksh)がどちらもない理由を説明します。command -v一部のBourneシェル実装にも追加されました(例:Solaris)。

今日の状態

現状はtypeBourneのようなすべてのシェルに共通です。 (@jarnoが指摘したように、以下の説明の「POSIXモードではないとき」またはAlmquistの一部の子孫のcommand -v警告/エラーに注意してください。シェル)。使用したい唯一のシェルです(そこにはシェルがなく、組み込まれているからです)。bashtcshwhichtypewhich

tcshシェルの外側のシェルでは、シェルzsh起動whichファイルの1つまたは一部に同じ名前のエイリアスまたは関数がなく、それに対して定義されたエイリアスまたは関数がない限り、~/.cshrcこれは~/.bashrcユーザーに通知することも、そうでない場合もあります。間違ったことを教えてください。$PATH~/.cshrc

特定の名前のすべてのコマンドを知りたい場合は、移植可能なものはありません。whereintcshまたは inzshまたは in ksh93 およびその他のシェルを使用でき、それらを組み合わせることができます。type -abashzshwhence -atypewhich -a

提案

実行可能ファイルのパス名を取得します。

スクリプトから実行可能ファイルのパス名を取得するには、いくつかの考慮事項があります。

ls=$(command -v ls)

標準的なアプローチになります。

しかし、いくつかの問題があります。

  • 実行ファイルを実行しないと、そのパスを知ることはできません。すべてtype、、、 ...経験的whichcommand -v方法を使用してパスを見つけます。コンポーネントを繰り返して、$PATH実行権限を持つディレクトリではなく最初のファイルを見つけます。ただし、シェルによっては、多くのコマンド(Bourne、AT&T ksh、zsh、ash...)は、システムコールがエラーを返さない$PATHまで順番に実行されます。execveたとえば、含まれていて$PATH実行/foo:/barしたい場合はls最初に実行を試み、/foo/lsそれ以外の場合は失敗します/bar/ls/foo/ls実行権限がないため、実行は失敗する可能性がありますが、有効な実行ファイルではないなど、さまざまな理由で実行が失敗する可能性があります。実行権限があるかどうかcommand -v lsを報告しますが、/foo/ls有効な実行ファイルではない場合は、/foo/ls実行がls実際に実行される可能性があります。/bar/ls/foo/ls
  • foo組み込み関数、関数、またはエイリアスかどうかを返しcommand -v fooますfoo。またはashなどの一部のシェルでは、pdksh空の文字列が含まれ、現在のディレクトリに実行可能ファイルが含まれている場合にzsh返されることがあります。場合によっては、これを考慮する必要があります。組み込み関数のリストは、シェルの実装(時にはbusyboxの組み込み関数)によって異なります。たとえば、環境で関数を取得できることを覚えておいてください。foo$PATHfoomountshbash
  • $PATH相対パスコンポーネントが含まれている場合(通常.または両方の現在のディレクトリを参照しますが、何でもできる空の文字列)、シェルによっては絶対パスがcommand -v cmd出力されないことがあります。したがって、走行中に得られた経路は、command -v他の場所に到達した後、もはや有効ではありません。cd
  • エピソード:ksh93シェルを使用している場合/opt/ast/bin(正確なパスはシステムによって異なる可能性があると思いますが)、$PATHksh93は追加の組み込み機能(chmod、、、cmp... cat)を提供しますが、そのパスが存在しない場合command -v chmodでも/opt/ast/bin/chmod存在しないものを返しますします。

コマンドが存在することを確認する

特定のコマンドが標準として存在することを確認するには、次のようにします。

if command -v given-command > /dev/null 2>&1; then
  echo given-command is available
else
  echo given-command is not available
fi

人々が使いたい場所which

(t)csh

cshと ではtcsh選択の余地は多くありません。ではこれが内蔵されているtcshので良いです。whichこれはcshシステムwhichコマンドであり、場合によっては目的の操作を実行できない場合があります。

特定のシェルでのみコマンドを検索する

これを使用するのが合理的である可能性がある状況は、シェルスクリプトで潜在的なシェル組み込み機能や関数を無視してコマンドのパスをwhich知りたい場合ですbashcshつまり、(例:または)、(例:、、)、(、)、またはシステムで使用でき、スクリプトではなく組み込み関数(例:または)です。tcshdashBournewhence -pkshzshcommand -evyashwhatis -prcakangawhichtcshzshwhichcsh

これらの条件が満たされると、以下が実行される。

echo=$(which echo)

echoシェルが組み込み/エイリアス/関数であるかどうかに$PATHかかわらず(極端な場合を除く)最初の項目へのパスを提供します。echo

他のシェルでは、以下を好みます。

  • 扱いにくいecho==echoまたはecho=$commands[echo]またはecho=${${:-echo}:c}
  • 変化の多く扱いにくい:echo=$(whence -p echo)
  • ヤッシュ:echo=$(command -ev echo)
  • RCアカンガ:(echo=`whatis -p echo`空白のあるパスを参照してください)
  • :set echo (type -fp echo)

やりたいことがあれば走るこのechoコマンドを使用すると、パスをインポートすることなく次のようにできます。

env echo this is not echoed by the builtin echo

tcshたとえば、組み込み関数の使用を防ぐには、を使用します。which

set Echo = "`env which echo`"

本当に外部コマンドが必要なとき

使用したい別の状況whichは実際には必要外部コマンド。 POSIXでは、すべてのシェル組み込みコマンド(たとえば)を外部コマンドとしても使用できるはずですが、残念ながら多くのシステムではcommandそうではありません。たとえば、このコマンドはLinuxベースのオペレーティングシステムではほとんど見つかりませんが、ほとんどcommandのオペレーティングシステムでは使用できます(オペレーティングシステムごとにオプションと動作が異なります)。commandwhich

外部コマンドが必要な状況は、POSIXシェルを呼び出さずにコマンドを実行したい場合です。

system("some command line")Cまたはさまざまな言語、...関数はpopen()シェルを呼び出してコマンドラインを解析するので、system("command -v my-cmd")その中で作業します。 1つの例外は、perlシェルにシェル特殊文字(スペースを除く)が表示されない場合、シェルが最適化されることです。これはバックティック演算子でも機能します。

$ perl -le 'print system "command -v emacs"'
-1
$ perl -le 'print system ":;command -v emacs"'
/usr/bin/emacs
0

$ perl -e 'print `command -v emacs`'
$ perl -e 'print `:;command -v emacs`'
/usr/bin/emacs

:;上記を追加すると、perlシェルが強制的に呼び出されます。を使用すると、whichこのトリックを使用する必要はありません。

おすすめ記事