Linux / コマンド / command - 存在確認と関数の検索を省いた実行

commandは、シェル関数の検索をスキップし、組み込みコマンドまたはPATHで見つけた外部コマンドを実行するシェルの組み込みコマンドです。同名のラッパー関数を作成した際に元のコマンドを呼び出す場合や、command -vで現在のシェルがコマンドを見つけられるか確認する場合に主に使用されます。

Bashでは-vおよび-Vでコマンド解釈情報を確認し、-pで標準ユーティリティを見つけるための基本検索パスを使用します。exec、env、builtinとは役割が異なるため、目的に応じて使い分ける必要があります。

commandコマンドとは?

シェル関数は外部コマンドと同じ名前を持つことがあります。この場合、関数内でその名前をそのまま呼び出すと、再帰的に自分自身を呼び出すことになり、無限ループに陥る可能性があります。commandを前に付けると、同名のシェル関数をスキップし、Bashの組み込みコマンドまたはPATHで見つけた実行ファイルを選択します。

command自体は、Bashをはじめとする複数のシェルが提供する組み込み機能です。POSIXでも定義されていますが、詳細な出力や拡張動作はシェルごとに異なる場合があります。

基本的な文法

command [-pVv] コマンド [引数...]

オプションなしで使用すると、指定したコマンドを実行します。

command ls -la
command printf '%s\n' 'hello'

ほとんどの状況では、コマンドを直接実行したのと同じように見えます。違いは、同じ名前のシェル関数があるときに明らかになります。

シェル関数の中で元のコマンドを呼び出す

練習では、まず bash --noprofile --norc で設定ファイルを読まない別の Bash を起動します。既存の ls エイリアスは関数定義の読み取りにも影響するため、この別シェルで type ls を確認し、エイリアスのない状態で次の関数を定義してください。定義後に ls -d . を実行すると、メッセージと現在のディレクトリ名が表示されます。練習後に exit すれば元のシェルへ戻り、元の設定を変えずに済みます。

次の関数はlsを実行する前にメッセージを出力し、その後元のlsを呼び出します。

ls() {
  printf '%s\n' 'リストを出力します.'
  command ls --color=auto "$@"
}

関数の中でls "$@"と書くと、同じ関数が再び呼び出されます。command lsは関数検索をスキップするので、Bashの組み込みコマンドまたはPATHの外部lsが実行されます。

"$@"は関数に渡された引数を元の境界のまま渡します。$*や引用符なしの$@に変えると、空白やワイルドカードを含む引数が壊れる可能性があります。

command -vでコマンドの存在を確認する

-vは指定した名前が現在のシェルでどのように解釈されるかを簡潔に出力します。コマンドが見つかれば成功状態を、見つからなければ失敗状態を返すので、スクリプトで存在をチェックするのに適しています。

command -v git
command -v cd
command -v printf

出力は実行ファイルのパスだけでなく、組み込みコマンドの名前、関数、またはエイリアスに対する説明である場合があります。したがって、結果を無条件にファイルパスだと仮定してはいけません。

if command -v curl >/dev/null 2>&1; then
  printf '%s\n' 'curlを使用することができます.'
else
  printf '%s\n' 'curl見つかりませんでした.' >&2
fi

command -Vで詳細情報を確認する

-Vは-vよりも人間が読みやすい詳細な説明を出力します。

command -V ls
command -V cd
command -V deploy

自動化では文章形式がシェルやロケールによって変わる可能性があるため、-Vの出力を解析しないほうが良いです。コマンドの存在確認にはcommand -vの終了ステータスを使用してください。

command -pとデフォルト検索パス

-pは標準ユーティリティを見つけられるようにシェルが提供するデフォルトのPATH値を使用します。ユーザーが変更したPATHが信頼できない場合や標準コマンドを探す必要がある場合に使用できます。

command -p getconf PATH
command -p ls

デフォルトパスの実際の値はシステムによって異なります。-pが特定の絶対パスや特定のプロバイダのプログラムを保証するわけではないので、セキュリティが重要なスクリプトでは実行環境と信頼の境界を別途設計する必要があります。

commandオプションまとめ

オプション 説明
-v コマンドを実行せずに、現在のシェルの解釈結果を簡潔に出力します。
-V コマンドがどのように解釈されるかを詳しく説明します。
-p 標準ユーティリティを見つけるための基本検索パスを使ってコマンドを実行します。

エイリアスをバイパスする際の注意点

エイリアスの展開はコマンドの実行より前のシェル構文処理の段階で行われます。対話型Bashではcommand lsは通常、lsエイリアスの代わりに実際のコマンドを実行するために使用されますが、エイリアス定義やシェルの展開コンテキストまで含めて制御する汎用引用ツールだと考えてはいけません。

現在の名前のすべての候補はまずtype -aで確認できます。

type -a ls
command -V ls

単に現在のエイリアスを適用しないようにするには、名前をバックスラッシュで入力するか、確認した絶対パスを実行する方法もあります。

\ls
/usr/bin/ls

バックスラッシュはエイリアス展開だけを避け、同名の関数は引き続き呼び出され得ます。候補の違いは type の種類判定と全候補の表示で確認できます。絶対パスはディストリビューションやシステム構造によって異なる場合があるため、ハードコーディングする前に確認する必要があります。

command、builtin、exec、env の比較

コマンド 主な役割
command 名前 同じ名前のシェル関数の検索をスキップして、組み込みコマンドまたは PATH コマンドを実行します。
builtin 名前 同じ名前の Bash 組み込みコマンドを明示的に実行します。
exec 名前 現在のシェルプロセスを新しいプログラムに置き換えます。成功すると元のシェルには戻りません。
env VAR=値 名前 環境変数を調整した環境で外部コマンドを実行します。
type 名前 コマンドを実行せずに、現在の Bash が名前をどのように解釈するかを表示します。

たとえば、関数内で同じ名前の Bash 組み込みコマンドを必ず呼び出したい場合は builtin の方が明確で、関数以外の外部プログラムまで許可したい場合は command が適しています。

スクリプトで command -v を使用する場合の原則

出力を破棄して終了ステータスを確認する

if ! command -v rsync >/dev/null 2>&1; then
  printf '%s\n' 'rsync必要です.' >&2
  exit 1
fi

この方法は出力形式に依存しません。エラーメッセージまで隠す場合は、標準出力と標準エラーの両方をリダイレクトする必要があります。

実行可能な通常ファイルが必要な場合は追加で確認する

command -vが成功しても、結果はエイリアス、関数、または組み込みコマンドである可能性があります。必ず外部の実行ファイルが必要な場合は、結果がパスであるかどうか、実行可能な通常ファイルかどうかをさらに確認します。

path=$(command -v mytool) || exit 1

if [ -f "$path" ] && [ -x "$path" ]; then
  printf '実行ファイル: %s\n' "$path"
fi

mytool は確認するプログラム名に置き換えます。この判定は、返された文字列がファイルとして存在するかを調べる部分の例です。同名の関数・組み込みコマンドとカレントディレクトリのファイルが重なる場合には、実際に選択される対象まで証明できません。Bash で外部ファイルだけを探すには type -P mytool などで確認してください。

TOCTOU(確認時と使用時の競合)の問題を理解する

TOCTOU は Time Of Check To Time Of Use の略で、確認した時点と使用する時点の間に状態が変わる問題です。コマンドの存在を確認してから実行すると、その間にPATHやファイルが変更される可能性があります。単純なユーザー案内には十分ですが、セキュリティが重要な環境では、確認結果だけを信頼せず、制御されたパスと権限を使用する必要があります。

終了ステータス

コマンド実行モードでは、Bash で対象が見つからない場合は 127、見つかっても実行できない場合は通常 126 を返し、実行できた場合は対象コマンドの終了ステータスを返します。すべてのエラーが 127 になるわけではありません。-vと-Vは、名前が見つかれば成功、見つからなければ失敗します。

command false
printf 'status=%d\n' "$?"

command does-not-exist
printf 'status=%d\n' "$?"

終了ステータスは実行したコマンドの意味に従うため、単に0以外の値だからといってcommand自体が壊れているわけではありません。

よくある質問

commandは外部コマンドだけを実行しますか?

いいえ。Bashではシェル関数を飛ばした後、組み込みコマンドやPATHで見つかった外部コマンドを実行します。外部ファイルだけを探す場合はtype -Pのようなツールでパスを確認する必要があります。

command -vの結果は常に絶対パスですか?

いいえ。エイリアス、関数、予約語、組み込みコマンドや実行ファイルの結果が出ることがあります。コマンドの存在確認には終了ステータスを使用してください。

sudo command ...の形式で使用できますか?

commandはシェル組み込みコマンドであるため、sudoのように別の実行ファイルとして直接実行できない場合があります。通常は必要な外部コマンドをsudoに直接渡すか、シェル文法が必要な場合は引用やセキュリティへの影響を理解した上でシェルを明示的に実行する必要があります。

whichよりもcommand -vが良い理由は何ですか?

command -vはシェルが提供し、現在のシェルのコマンド解釈を反映します。whichは外部プログラムであることが多く、実装によって差があるため、エイリアスや関数を正確に処理できない場合があります。

関数を無視して組み込みコマンドだけを実行するにはどうすればよいですか?

Bashではbuiltin 名前を使用します。command 名前は組み込みコマンドだけでなく、PATHの外部コマンドも実行できます。

他のシェルでも同じオプションを使用できますか?

-p、-v、-Vの核心的動作はPOSIXで定義されていますが、出力文言や拡張機能はシェルごとに異なる場合があります。自動化では対象シェルを決め、その文書を確認してください。

まとめ

commandはシェル関数で隠されている元のコマンドを呼び出し、現在のシェルでコマンドを見つけられるか確認するのに有用です。実行にはcommand 名前、存在確認にはcommand -v 名前、人が読める詳細説明にはcommand -V 名前を使用します。

すべてのコマンドの候補と種類を調査する際は、type -aを一緒に使用してください。その他の基本コマンドは、Linuxコマンド一覧と概要で確認できます。

公式文書を参照