シェルがコマンドを実行する仕組みを解説:内部処理の流れを図解してみた
はじめに
ターミナルでコマンドを入力すると、指定した動作が実現します。
しかし、このコマンドはそもそもどうやって機能しているのでしょうか?
コマンドはシェルがもともと持っているビルトインコマンドか外部コマンドかによって動作の流れが異なります。
この記事では、コマンドがビルトインの場合と外部の場合でどのように異なるのか、解説していきます。
この点が理解できると、プロセスがどのようにコマンドを処理するのかをより理解することができます。
コマンドの正体
先に結論:コマンドの正体は主に3種類
| コマンドの種類 | 例 | 実体 | 実行方法 |
|---|---|---|---|
| ビルトインコマンド |
cd, echo, exit
|
シェルに埋め込まれた関数 | シェルが直接呼び出す |
| 外部バイナリ |
ls, grep, python
|
実行ファイル(ELFやMach-Oなど) | OSが fork + exec で実行 |
| シェルスクリプト |
script.sh, npm(実はスクリプト) |
テキストファイル(スクリプト) | インタプリタが読み取って実行 |
そもそもコマンドとは何でしょうか?
例えば、以下のような操作をターミナルに入力したことがあるでしょう。
$ ls
このように、コマンドとは、ユーザーがシェルに入力する命令のことを指します。
シェルはこの命令を受け取ると、それが自分の内部で処理できるビルトインコマンドか、
ファイルシステム上の外部コマンドかを判断し、適切な方法で実行します。
例えば上記の ls は、/bin/ls に存在する実行ファイル(バイナリ)を実行する外部コマンドです。
シェルがコマンドを処理する方法には、主に次の2通りがあります。
- ビルトインコマンド:シェルが内部に持っている命令(例:cd、exit など)
- 外部コマンド:ファイルシステム上の実行可能ファイル(例:ls、cat など)
この違いを理解することで、コマンド実行時にどのようにプロセスが生成されるかや、
なぜ一部のコマンドだけは環境に直接影響を与えるのかといった仕組みも見えてきます。
※ コマンド = ファイル(とは限らない)
コマンドが「ファイルで中身がスクリプト」な場合もあり、
コマンドが「実行可能なバイナリ(機械語)」や「シェル内のビルトイン関数」の場合もあります。
そのため、必ずしもコマンド = ファイルとは言い切れません。
1. ビルトインコマンド
ビルトインコマンドとは、シェル自体が内部に持っている命令です。
実行に際しては、新しいプロセスを作ることなく、現在のシェルプロセス内で直接処理されます。
代表的なビルトインコマンドの例
-
cd: ディレクトリを移動 -
echo: メッセージを表示 -
export: 環境変数を設定 -
exit: シェルを終了
ビルトインコマンドの特徴
- 高速:外部ファイルを探して起動する手間がないため処理が速い
- プロセスを作らない:新たなプロセスを起こさず、シェル内で完結
ターミナル起動時にシェル(bash や zsh など)がストレージからメモリに展開されてプロセスになると、
その時点で既にビルトインコマンドの機能はシェルプロセス内に組み込まれています。
さらに、追加でストレージから cd や echo のようなビルトインコマンドのファイルを読み込むことがなく、既存のシェルプロセスの中で処理が実行されるため、その分処理が早くなります。
2. 外部コマンド
外部コマンドとは、ファイルシステム上に存在する実行ファイルを呼び出して動作するコマンドです。
このファイルは PATH 環境変数で指定されたディレクトリの中から探されます。
外部コマンドの例
-
ls: ディレクトリの内容を表示 -
cat: ファイルの中身を表示 -
grep: テキスト検索
環境変数とは
環境変数とは、プロセスごとに保持される設定情報の集合であり、シェルやコマンド実行時の挙動を制御するために使われます。
例えば、以下のような情報が含まれます。
-
PATH: 実行可能ファイルを探すディレクトリの一覧 -
HOME: 現在のユーザーのホームディレクトリ -
SHELL: 使用しているシェルのパス
$ echo $HOME
/Users/username
PATHとは
PATH は最も重要な環境変数の1つで、外部コマンドを探すために参照されるディレクトリのリストです。
: で区切られた複数のパスを持ち、シェルは左から順番にコマンドを探していきます。
$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin
コマンドごとの処理の流れ
外部コマンドは主に2種類に分けられます。
① 外部バイナリ
② シェルスクリプト
1. ビルトインコマンドのケース
処理イメージ

※ 筆者による手書きメモ
(例)cd /tmp
- シェルプロセスが
cdというコマンドを読み取る - シェル内部の「ビルトインコマンドテーブル」を参照
-
cdはビルトインなので、fork せずに自分の関数を呼び出して実行 -
chdir("/tmp")などのシステムコールが現在のプロセス内で実行される
※ ビルトインは関数としてforkされずに実行される
ビルトインコマンドは「現在のシェルプロセスの中」で、その関数を直接呼び出して処理されます。
つまり、fork() も exec() も不要ということです。
2. 外部コマンドのケース
2-1. 外部バイナリのケース
処理イメージ

※ 筆者による手書きメモ
(例)ls
- シェルが ls をビルトインで見つけられない
- PATH に従って
/bin/lsなどをシェル自身が探す - fork() で子プロセスを作り、execve("/bin/ls") でプロセスの中身を置き換える
- 結果を待って、親シェルに戻る
2-2. シェルスクリプトのケース
処理イメージ

※ 筆者による手書きメモ
(例)./script.sh
- シェル内でビルトインかどうかを確認
- 明示的に指定されたパス(./script.sh など)を使用してファイルの存在を確認
- カーネルがファイルの先頭にシバン(
#!)があることを確認し、スクリプトファイルだと判断 - シェルが fork() で子プロセスとして生成
- OSカーネルが
script.shの先頭にあるシバン(#!)を確認し、スクリプトファイルだと判断 - 子プロセスが execve("script.sh") を呼び出し、カーネルがシバンを解釈して
/bin/bashを起動し、script.shを引数として渡す -
/bin/bashがファイルscript.shを引数として受け取り、自分で開いて中身を逐次実行 - 終了すると、子プロセスは終了
※ シバンがなければ OS は実行できずエラーになります。
結果として Permission denied や command not found になる場合もあります。
バイナリ(実行)ファイルとビルトインの違い
$ type cd
cd is a shell builtin
$ type ls
ls is /bin/ls
| 視点 | ビルトインコマンド | 外部コマンド |
|---|---|---|
| 定義場所 | シェル本体(実行ファイル内) | ストレージ上の別ファイル(/bin/ls など) |
| メモリ展開 | シェル起動時に一緒に読み込まれる | 実行時に exec() で読み込まれる |
| プロセス生成 | 不要(同一プロセス内で処理) | 必要(fork + exec) |
| 例 |
cd, echo, export, alias, read, source
|
ls, cat, python, grep
|
スクリプトの先頭にシバンがないと、OSは「どう実行すればいいかわからない」
※ シバンがなければ OS は実行できずエラーになります
結果として Permission denied や command not found になる場合もある
あるいはシェルが直接 source で読み込まないと実行できない
まとめ
シェルプロセスが実行するコマンドには、大きく分けてビルトインコマンドと外部コマンドがあります。さらに外部コマンドには、バイナリファイルとスクリプトファイルという2つの形態が存在します。
私たちが普段ターミナルに入力する一見シンプルなコマンドも、実際には プロセス、環境変数 PATH、シェルの機能、そしてOSカーネル など、さまざまな仕組みが連携することで動いています。
こうした処理の流れを理解することで、「すべてはファイルである(Everything is a file)」というUnixの哲学や、プロセスの基本的な仕組みにも自然と触れることができます。
今後、プロセス管理やシステムプログラミングを学ぶ上でも役立つ基礎知識なので、ぜひこのタイミングでしっかりと押さえておきましょう!
最後までお読みいただき、ありがとうございました。
参考URL
Discussion