PATH上でプログラムの完全なパスを確実に見つけるにはどうすればよいですか?


12

PATHシェルスクリプトを使用して、特定のプログラムのパスを見つける必要があります。パスは次のいずれかに後で渡すことができるプログラムの実際の完全なパスでなければなりませんexec*検索しない機能、PATH例えば、自分自身をexecv

のようなプログラムがあります。これらのプログラムkillは、実際のプログラムとシェル組み込みとして同時に利用できます。この場合、実際のプログラムへの完全なパスが必要です。

セクション2.9.1.1、POSIX標準のコマンド検索と実行でPATH指定されているようにプログラムを見つけることができるユーティリティがいくつかあります。

がありwhich、これは標準の一部ではありません。一部のシステムでは通常のプログラムである場合がありますが、一部のシェルでは組み込みです。ほとんどのシステムとシェルで使用できるようですが、組み込みバージョンのシェルも、実行可能ファイルへのパスではなく、組み込みの名前を返すだけです。また、いかなる方法でも標準化されておらず、出力を返したり、異なるオプションを使用したりする場合があります。

bash# which kill
/usr/bin/kill
dash# which kill
/usr/bin/kill
fish# which kill
/usr/bin/kill
mksh# which kill
/usr/bin/kill
tcsh# which kill
kill: shell built-in command.
zsh# which kill
kill: shell built-in command

あるwhenceいくつかのシェルのビルトインされています、。ただし、多くのシェルでは使用できません。プログラムへのパスではなく、組み込みの名前も返します。A -pをwhenceに渡して、この動作を変更できます。

bash# whence kill
bash: whence: command not found
dash# whence kill
dash: 1: whence: not found
fish# whence kill
fish: Unknown command 'whence'
mksh# whence kill
kill
mksh# whence -p kill
/usr/bin/kill
tcsh# whence kill
whence: Command not found.
zsh# whence kill
kill
zsh# whence -p kill
/usr/bin/kill

commandPOSIX:2008で指定されているビルトインがあります。残念ながら、通常のコマンドとビルトインも検索し、同じ名前のビルトインによってシャドウされているプログラムへのパスの代わりにビルトインの名前を返します。いくつかの古いシェルはまだ実装していません。

bash# command -v kill
kill
dash# command -v kill
kill
fish# command -v kill
/usr/bin/kill
mksh# command -v kill
kill
tcsh# command -v kill
command: Command not found.
zsh# command -v kill
kill

enableがPOSIXで指定されているかどうかはわかりませんが、指定されている場合はenable -n which、のシェルビルトインを無効にするために使用できますwhich
ミューザー

そして、あるrealpath
Ipor Sircer

私の処分で私が持っているシェルで@Muzer、enableのみで提供されるbashzsh
セバスチャン・シュレイダー

1
すべてのシェルではなく、スクリプトを実行している特定のシェルに実現可能な方法が必要です。スクリプトはランダムシェルによって実行されるのではなく、具体的にはshebang行で指定されたシェルによって実行されます。そうは言っても、bashの場合はになりますtype -p。bashとdashの両方commandを使用すると、同じ名前の関数または組み込み関数がある場合でも、実際の実行可能ファイルを実行するコマンドを言うことができます。
AlexP

1
@AlexP commandは、関数(およびエイリアス)をスキップしますが、Qが正しく述べているように、ビルトインはスキップしません。また、すべてのシステムに特定のシェルまたはPOSIXシェルを取得するパスがないため、常にシバンを使用できるわけではありません。
-dave_thompson_085

回答:


11

自分で検索してください。

export IFS=":"
[ -z "${1}" ] && exit 1
for dir in $PATH
do if [ -x "${dir}/${1}" ]
   then echo "${dir}/${1}"
        exit 0
   fi
done
echo ${1} not found
exit 1

でテストしbashdashkshmkshzsh

更新

上記はスタンドアロンスクリプトに適していますが、これをより大きなスクリプトに埋め込むことを計画している場合は、次のようなものを使用できます。

function find_path() {
   IFS_SAVE="${IFS}"
   export IFS=":"
   [ -z "${1}" ] && exit 1
   for dir in $PATH
   do if [ -x "${dir}/${1}" ]
      then echo "${dir}/${1}"
           export IFS="${IFS_SAVE}"
           return 0
      fi
   done
   export IFS="${IFS_SAVE}"
   echo ${1} not found
   return 1
}

これは、そうですIFS、の一致を検出した後に復元さもスワップexit「でのreturnS」


1
たぶん-fではなく-x?
ジェフシャラー

@JeffSchallerの良い点、非実行ファイルを選択する理由はありません。
ザカリーブレイディ

3
これをより大きなシェルスクリプトに統合する場合(おそらく、本来の目的でスクリプトにするのではなく)、後でIFSの古い値を復元することができます。スクリプトの残り...
psmears

IFS変数をエクスポートする理由 これをローカルシェルに設定するだけでは十分ではありませんか?ローカルといえば、local IFSポータブルですか?他の何かが同じ方法でIFSを保存している場合、上記の相互作用が不十分になる可能性があります。見るとSEのこの質問localPOSIXされていないにも関わらず、ほとんどのシェルのために働くことがあります。元のバージョンを(…)サブシェルに入れることでも同様に機能する場合があります。
MvG
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.