環境変数にパスを通すとコマンドが認識されるワケ

# はじめに

学びはじめのころ。

- なんかコマンドが認識されないが...？
- 先輩が言うとおりに、環境変数に何らかのパスを追加したら解消したが...？
- 仕組みが分かってないけど「解消したし、まあええか」で終了

となりがち（個人的見解）な、「パスを通す」の自分の理解度を確認するために説明してみます。

# 目次
- [よくあるエラー](#よくあるエラー)
- [なんで環境変数にパスを通すとコマンドが認識されるの？](#なんで環境変数にパスを通すとコマンドが認識されるの)
- [いろいろある環境変数](#いろいろある環境変数)
- [コマンド実行の流れ](#コマンド実行の流れ)
- [コマンドには種類がある](#コマンドには種類がある)
- [Linuxにおいて環境変数を設定するための方法](#linuxにおいて環境変数を設定するための方法)
  - [方法１：シェルの途中でPATHを通す](#方法１シェルの途中でpathを通す)
  - [方法２：初期化ファイルでPATHを通す](#方法２初期化ファイルでpathを通す)
- [コラム：外部コマンドのプログラムを確認してみる](#コラム外部コマンドのプログラムを確認してみる)
- [まとめ](#まとめ)
- [おわりに](#おわりに)

# よくあるエラー

git コマンド使いたいのに...。
```bash
$ git status
'git' is not recognized as an internal or external command, 
operable program or batch file.
```

npm run buildしたいのに...。
```bash
$ npm run build 
‘npm’ は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません
```

これらは環境変数にパスを通すとだいたい解決します。

# なんで環境変数にパスを通すとコマンドが認識されるの？

なぜなら、**シェルは環境変数 `PATH` に登録されているディレクトリのパスからコマンドを探し出すから** です。

# いろいろある環境変数

今回のテーマである環境変数 `PATH` 以外にも、環境変数にはいくつかの種類が定義されている。現在設定されている環境変数は以下のように一覧表示できる。

~~~bash
$ env # 若干の違いがあるけど `printenv` コマンドでも表示可能
SHELL=/bin/bash
NAME=_mi
PWD=/usr/bin
LOGNAME=_mi
...以下略
~~~

シェルスクリプトで使いやすいものをピックアップすると、以下とかでしょうか？

| **環境変数** | **概要** |
| :--: | :--: |
| `PATH` | シェルがコマンドを探すときの検索先ディレクトリリスト |
| `HOME` | 現在のログインユーザーのホームディレクトリのパスを表す |
| `PWD` | 現在の作業ディレクトリのパスを表す |
| `EDITOR` | 使用するテキストエディタの名前を指定する |
| `USER` | 現在のログインユーザー名を表す |
| `GROUP` | 現在のログインユーザーが所属しているグループの名前を表す |

これ以外にもたくさんの環境変数がありますが、シェルがコマンドを探しだすためには `PATH` への登録が必要なのです。

# コマンド実行の流れ

コマンドは以下のような流れで実行される。

1. ユーザーが端末（ターミナル）にコマンドを入力する
1. 端末（ターミナル）がシェルに入力された文字列を渡す
1. シェルは受け取った文字列（コマンド）を解析し、実行するべきコマンドを特定する
1. シェルはコマンドを探すために環境変数 `PATH` を探しに行く
1. シェルは `PATH` の中で最初にマッチしたコマンド（プログラム）を見つける
1. シェルがカーネルに対してコマンドの実行要求を出す
1. カーネルはコマンドの実行ファイル（プログラム）を見つける
1. カーネルがコマンドを実行するために、CPUやメモリなどを割り当てる
1. カーネルがコマンドを実行する
1. カーネルは実行結果を受け取り、デバイスや端末（ターミナル）に出力する

※ `PATH` には複数のディレクトリがコロンで区切られて含まれているが、これはコマンドを探す順番が指定されている

![コマンド実行の流れcreatedbymmk.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3388832/b7fb1be6-91c2-a7fb-437b-c4ee5e1475b4.png)


なので、環境変数の `PATH` にコマンドの実行可能ファイルが存在していない場合は『そんなコマンドないよぉ！！』とエラー出してくるわけ。

そういう意味で、**パスを通すとは、新しいディレクトリパスを既存のPATH環境変数に追加する（コマンドの検索先を追加する）こと** を指している。

# コマンドには種類がある

コマンドは大きく分けて以下の３種類がある。

- エイリアス：コマンドの別名
- ビルトインコマンド：シェルの中に最初から組み込まれているコマンド
- 外部コマンド：シェルの外部にファイルとして用意されているコマンド

コマンドが上記のどれに該当するかは `type` コマンドで確かめることができる。

~~~bash
# 使い方
$ type コマンド名

# 例えばこんな感じ
$ type grep
grep is aliased to 'grep --color=auto' # エイリアスだ！

$ type cd
cd is a shell builtin # ビルトインコマンドだ！

$ type jq
jq is /usr/bin/jq # 外部コマンドだ！
~~~

エイリアスやビルトインコマンドを使用する際には環境変数に `PATH` を通す必要がない。`PATH` の設定は、システム上の**外部コマンドを実行する際にのみ**関係することが理解できたらOK！

# Linuxにおいて環境変数を設定するための方法

今回はいったん以下２つの方法を取り上げて、環境変数への `PATH` を通すを説明します。

- 方法１：シェルの途中で `PATH` を通す
- 方法２：初期化ファイル（`.profile` や `.bash_profile` など）で `PATH` を通す

## 方法１：シェルの途中でPATHを通す

環境変数は、シェル変数の中でも特に外部プロセスやシステム全体で使われる変数のこと。

なのでまずシェル変数について触れますが、シェル変数は文字列や数値などの値を保存する機能であり、プログラマにとっては身近なものですね。

- シェル変数の定義：`変数名=値`
- シェル変数の参照：`$変数名`

で、環境変数はそんなシェル変数の中の一部分にあたります。

シェル変数との具体的な違いはというと、**子プロセスに変数の値が受け継がれる**という点。

### 環境変数がサブシェルでも引き継がれることを確認する

まず、シェル変数を定義してサブシェルに引き継がれないことを確認する。

~~~bash
$ hoge=hogeee # シェル変数を定義
$ echo $hoge
hogeee # 出力される

$ bash # サブシェルの起動
$ echo $hoge 
# 何も表示されない
~~~

上記で、プロセス上で定義したシェル変数は子プロセスには引き継がれないことが確認できたところで、環境変数を定義してみましょう。

~~~bash
$ fuga=fugaaa # シェル変数を定義
$ export fuga # シェル変数fugaを環境変数としても定義

$ echo fuga
fugaaa # 出力される

$ bash # サブシェルの起動
$ echo fuga
fugaaa # 出力される
~~~

環境変数に定義したら、サブシェル起動後の子プロセス上でも使用できました◎

注意すべき点は、環境変数に定義したとしても親プロセスでは使用できない点ですね。（初期化ファイルに登録しておく必要がある）

### サンプルスクリプト

`jq` コマンドがマシンにインストールされているとして、シェルスクリプト上で `jq` コマンドを実行するために `jq` コマンドのパスを通すサンプルスクリプトです。

~~~bash:jqsample.sh
#!/bin/bash

# PATHにjqコマンドのディレクトリパスを追加する
export PATH="$PATH:/path/to/jq"

# jqコマンドを実行する
result=$(echo '{"name": "_mi", "age": 15}' | jq '.name')
echo "Result: $result"
~~~

実行してみると、

~~~bash
$ bash jqsample.sh
Result: "_mi" # 出力される
~~~

こんな感じで、シェルの途中でコマンドまでのパスを環境変数として登録し、利用するという方法が１つですね。

## 方法２：初期化ファイルでPATHを通す

上記の登録方法はあくまで操作しているbashプロセスに対する設定であるため、別のプロセスには反映されない（※ 子プロセスには反映される）という問題がある。毎回こんな記述したくないよぉ！という場合は、初期化ファイルに登録するといい。

登録しておけば（読み込みのタイミングが違うので注意が必要だけど）シェルの起動時に設定値が自動で読み込まれるので、エイリアスや環境変数を毎回設定しなくてよくなる。

初期化のための設定ファイルである `.bash_profile` や `.bashrc` については、以下記事がとても分かりやすく書いてくれているので参照されたし。


https://qiita.com/hirokishirai/items/5a529c8395c4b336bf31

https://qiita.com/magicant/items/d3bb7ea1192e63fba850

https://blog1.mammb.com/entry/2019/12/01/090000

# コラム：外部コマンドのプログラムを確認してみる

この前インストールした `jq` コマンドを見てみるよ。

~~~bash
$ type jq # パスを確認する
jq is /usr/bin/jq 

$ cd /usr/bin/ # 移動

$ ls -l jq # ファイルの読み取り権限を確認
-rwxr-xr-x 1 root root 30872 Mar 24  2022 jq 

$ cat jq # 中身の確認
// あ...文字化け...
~~~

`jq` コマンドはバイナリ形式の実行ファイルだからそりゃあテキストエディタでは正しく表示することはできませんねWWW

てなわけで方向転換。

~~~bash
$ apt policy jq # インストール元を確認する
jq:
  Installed: 1.6-2.1ubuntu3
  Candidate: 1.6-2.1ubuntu3
  Version table:
 *** 1.6-2.1ubuntu3 500
        500 http://ftp.jaist.ac.jp/pub/Linux/ubuntu jammy/main amd64 Packages
        100 /var/lib/dpkg/status
~~~

どうやらインストールされている `jq` パッケージのインストール元は、
http://ftp.jaist.ac.jp/pub/Linux/ubuntu になっていることが確認できたので見に行く。

が、見てもよう分からんとなり、Microsoft Copilotに聞いたらそこから見るの無理ぽい回答されたので諦めました。己の弱さを今日もまた知った。

> なお、https://ftp.jaist.ac.jp/pub/Linux/ubuntu/ で提供されているのはUbuntuのパッケージリポジトリであり、特定のコマンドの実行ファイルを直接探すのではなく、パッケージマネージャ（aptコマンドなど）を通じてソフトウェアをインストールするためのものです。したがって、このURLから直接jqコマンドの実行ファイルを見つけることは難しいです。

でも探してみたら [jqの公式リポジトリ](https://github.com/jqlang/jq) があった。`src` 直下のファイル見ると、なんかこの辺かなーという感じで、主にC言語のプログラムファイルがありました。中身は何書いてあるか１ミリも分からない。

コマンドのプログラムファイルが見たかったらおとなしく [公式リファレンス](https://yujiorama.github.io/unofficial-translations/jq/l10n/jp/manual/) を見ましょう！！

# まとめ

パスを通す = 環境変数 `PATH` に外部コマンドまでのパスを新たに追加して、ファイル名だけでコマンドを実行できるようにすること。

パスを通しておけば、シェルで打ち込んだときにコマンドを探しに行ってくれるのでコマンド（プログラム）が実行される。

# おわりに

実際にコマンド打ちながら確かめる記事書くの、いいアウトプットになっていいね！