# コマンドの実行とその環境

この節ではコマンドがどのように実行されるかを説明します。

## 単純コマンドの実行

単純コマンドは以下の手順に従って実行されます。

1. 単純コマンドに含まれる、変数代入とリダイレクト以外のトークンを全て​展開します。展開エラーが発生した場合は、この単純コマンドの実行は中止されます (このとき単純コマンドの終了ステータスは非 0 です)。
   以下、展開の結果得られた最初の単語をコマンド名、それ以外の単語をコマンド引数と呼びます。得られた単語が一つの場合は、コマンド引数は存在しません。得られた単語が一つもない場合は、コマンド名もコマンド引数も存在しません。

2. コマンド名が存在する場合、単純コマンドに対する​リダイレクトを実行します。リダイレクトに含まれるトークンの展開はここで行われます。リダイレクトエラーが発生した場合は、この単純コマンドの実行は中止されます (このとき単純コマンドの終了ステータスは非 0 です)。リダイレクトに含まれるトークンの展開時のエラーはリダイレクトエラーに含まれます。

   注 | Yash 以外のシェルではリダイレクトの実行タイミングが異なることがあります。POSIX では、コマンド名が存在しない場合または​特殊組込みである場合にリダイレクトと代入のどちらを先に行うか規定されていません。

3. 単純コマンドに含まれる変数代入を実行します (配列の代入を含む)。それぞれの変数代入について、値が展開され、指定された名前の​変数に代入されます。代入エラーが発生した場合は、この単純コマンドの実行は中止されます (このとき単純コマンドの終了ステータスは非 0 です)。代入される値の展開時のエラーは代入エラーに含まれます。

   - コマンド名が存在しないか、あるいはコマンド名が​特殊組込みを示している場合は、変数代入は恒久的です。すなわち、代入の結果はこの単純コマンドの実行が終わった後もそのまま残ります。

   - それ以外の場合は、変数代入は一時的です。すなわち、代入の効果はこの単純コマンドの実行中のみ有効で、実行が終わった後に代入は取り消されます。

   コマンド名が指定された場合または all-export オプションが有効な場合は、代入される変数は自動的に​エクスポート対象になります。

   注 | Yash 以外のシェルでは代入の動作が異なることがあります。特殊組込みまたは関数では変数はエクスポート対象にならないかもしれません。また関数の実行終了後も変数が残るかもしれません。

4. コマンド名が存在しない場合は、サブシェルで​リダイレクトを実行し、そこで単純コマンドの実行を終了します。リダイレクトエラーが発生した場合、単純コマンドの終了ステータスは非 0 になります。代入の途中で​コマンド置換が行われた時は、最後のコマンド置換のコマンドの終了ステータスが単純コマンドの終了ステータスになります。それ以外の場合、終了ステータスは 0 になります。

5. 後述のコマンドの検索の仕方に従って実行すべきコマンドを特定し、そのコマンドを実行します。

   - コマンドが外部コマンドの場合は、コマンドはサブシェルで exec システムコールを呼び出すことにより実行されます。コマンド名とコマンド引数が起動するコマンドに渡されます。またエクスポート対象となっている変数が環境変数としてコマンドに渡されます。

   - コマンドが​組込みコマンドの場合は、コマンド引数を引数として組込みコマンドが実行されます。例外的に、​任意組込みコマンドの場合は、​POSIX 準拠モードにおいてはコマンドは実行されません。

   - コマンドが関数の場合は、その関数の内容が実行されます。コマンド引数が関数の引数として渡されます。

   実行したコマンドの終了ステータスがこの単純コマンドの終了ステータスになります。コマンドが見つからなかった場合は、コマンドは実行されず終了ステータスは 127 になります。コマンドが見つかったが起動に失敗した場合は、終了ステータスは 126 になります。コマンドが起動されたがシグナルによって中断された場合は、終了ステータスはそのシグナルの番号に 384 を足した数になります。POSIX 準拠モードで任意組込みコマンドが実行されなかった場合は、終了ステータスは 127 になります。

   注 | POSIX ではコマンドがシグナルによって中断された場合の終了ステータスは 128 より大きな数としか定められていないので、yash 以外のシェルでは終了ステータスが異なることがあります。

   非 POSIX 準拠モードにおいてコマンドが見つからなかったとき、コマンド `eval -i -- "${COMMAND_NOT_FOUND_HANDLER-}"` が実行されます。ただしこのとき​位置パラメータはコマンド名とコマンド引数に一時的に置き換えられます。またこのコマンドの実行中に定義されたローカル変数はこのコマンドの終了時に削除されます。このコマンドの実行時には `HANDLED` ローカル変数が空文字列を値としてあらかじめ定義されます。このコマンドの実行後にこの変数の値が空文字列でなくなっていれば、このコマンドの終了ステータスがこの単純コマンドの終了ステータスとなり、コマンドが見つからなかったことはエラーとはみなされません。

### コマンドの検索

単純コマンドで実行すべきコマンドは、展開で得られたコマンド名に基づいて以下の手順で特定されます。

1. コマンド名にスラッシュ (`/`) が含まれている場合は、それが実行すべき外部コマンドへのパス名であると特定されます。

2. コマンド名が​特殊組込みコマンドならば、その組込みコマンドが実行すべきコマンドとして特定されます。

3. コマンド名と同じ名前の関数が存在すれば、その関数が実行すべきコマンドとして特定されます。

4. コマンド名が​必須組込みコマンドまたは任意組込みコマンドならば、その組込みコマンドが実行すべきコマンドとして特定されます。

5. コマンド名が​拡張組込みコマンドならば、その組込みコマンドが実行すべきコマンドとして特定されます。(POSIX 準拠モードのときを除く)

6. `PATH` 変数の値に従って、実行すべき外部コマンドを検索しそのパス名を特定します。

   `PATH` 変数の値は、いくつかのディレクトリのパス名をコロン (`:`) で区切ったものとみなされます (空のパス名はシェルの作業ディレクトリを表しているものとみなします)。それらの各ディレクトリについて順に、ディレクトリの中にコマンド名と同じ名前の実行可能な通常のファイルがあるか調査します。そのようなファイルがあれば、そのファイルが実行すべき外部コマンドとして特定されます (ただし、コマンド名と同じ名前の代替組込みコマンドがあれば、代わりにその組込みコマンドが実行すべきコマンドとして特定されます)。どのディレクトリにもそのようなファイルが見つからなければ、実行すべきコマンドは見つからなかったものとみなされます。

外部コマンドの検索が成功しパス名が特定できた場合、そのパス名が絶対パスならば、シェルはそのパス名を記憶し、再び同じコマンドを実行する際に検索の手間を省きます。ただし、再びコマンドを実行しようとした際に、記憶しているパス名に実行可能ファイルが見当たらない場合は、検索をやり直します。シェルが記憶しているパス名は hash 組込みコマンドで管理できます。

## シェルの終了

シェルは、入力が終わりに達して全てのコマンドを解釈・実行し終えた時や、​exit 組込みコマンドを実行したときなどに終了します。シェルの終了ステータスは、シェルが最後に実行したコマンドの終了ステータスを 256 で割った余りです (一つもコマンドを実行しなかったときは 0)。

Trap 組込みコマンドでシェル終了時のハンドラが登録されている場合は、シェルが終了する直前にそのハンドラが実行されます。ただしこのハンドラ内で実行したコマンドはシェルの終了ステータスには影響しません。

対話モードでないシェルの実行中に下記エラーが発生した場合、シェルは直ちに終了します。このときシェルの終了ステータスは非 0 です。

- 文法エラーのためコマンドを解釈できないとき (シェルの初期化中を除く)

- POSIX 準拠モードで、​特殊組込みコマンドの実行中にエラーが発生したとき

- POSIX 準拠モードで、特殊組込みコマンドに対して​リダイレクトエラーが発生したとき

- 単純コマンドで代入エラーが発生したとき

- 展開エラーが発生したとき (シェルの初期化中を除く)

注 | Yash はそうではありませんが、コマンドの検索において実行すべきコマンドが見つからなかったときに直ちに終了するようなシェルもあります。

## 関数

関数は一つの複合コマンドを単純コマンドのように呼び出せるようにする機構です。関数は​関数定義コマンドによって定義でき、​単純コマンドによって実行できます。関数を削除するには unset 組込みコマンドを使います。

Yash には、シェルの起動時に最初から定義されている関数は一つもありません。

関数の実行は、関数の内容である複合コマンドを実行することによって行われます。関数の実行中は、関数の引数が​位置パラメータになります。それまでの位置パラメータは一時的に使えなくなりますが関数の実行が終わった時に元の位置パラメータに戻ります。

### ローカル変数

ローカル変数とは、関数の実行中にだけ有効な一時的な変数です。ローカル変数は typeset 組込みコマンドを使って作ることができます。また for ループの実行時に暗黙的に作られることもあります。関数の実行中に作られたローカル変数は関数の実行が終わった時に削除され、ローカル変数を作る前の元の変数の状態に戻ります。

関数内で定義したローカル変数は、関数の実行に先立って定義してあった同名の他の変数を隠蔽します。隠蔽された変数は、関数の実行が終わってローカル変数がなくなるまで使えなくなります。

関数の実行中でないときにローカル変数を作ることはできません。ローカル変数を作ろうとしても、通常の変数になります。

## コマンドの実行環境

シェルは実行時に以下の情報を保持します。

- 作業ディレクトリ

- 開いているファイル記述子

- ファイル作成マスク (umask)

- 受信時の挙動が 『無視』 に設定されたシグナルの集合 (trap)

- 環境変数

- リソース制限 (ulimit)

これらの情報はシェルが起動されたときに元のプログラムからシェルに受け継がれます。そしてシェルが起動する外部コマンドやサブシェルにもシェルから受け継がれます。

これらの情報は所定の組込みコマンド等を使って変更可能です。

### サブシェル

サブシェルは、実行中のシェルのプロセスのコピーです。サブシェルは括弧 `( )` で囲んだ​グルーピングや​パイプラインで使われます。

サブシェルはシェルのプロセスのコピーであるため、上記の情報の他にシェルで定義された関数やエイリアスなどの情報も元のシェルから受け継ぎます。ただし、

- Trap 組込みコマンドで登録したトラップの設定は、(受信時の挙動が 『無視』 のものを除き) サブシェルではすべて解除されます。(注)

- サブシェルでは​対話モードと​ジョブ制御は解除され、元のシェルのジョブの情報は受け継がれません。

サブシェルは元のシェルとは独立しているため、サブシェルでの作業ディレクトリの変更や変数代入は元のシェルに影響しません。

注 | Yash 以外のシェルでは、サブシェル内で実行されるコマンドが trap 組込みコマンドのみの場合にはトラップの設定を解除しないものもあります。