Yuki's Tech Blog

仕事で得た知見や勉強した技術を書きます。

PATHを通すについてざっくりまとめる

目次

PATHを通すとは

PATHを通すとは、「シェルの環境変数PATHにコマンドサーチパスを登録する」という意味です

そもそもシェルの環境変数とは何?

シェルの動作(振る舞い)を設定するためには,シェル変数あるいは環境変数に値を設定します。 2つの変数の特徴を以下にまとめます。

  • シェル変数

    • 設定されたシェルでしか参照できない変数
    • つまり、シェル変数は子プロセスに引き継がれない
    • 変数名に$をつけると参照できる
  • 環境変数

    • 設定されたシェルと、シェルで起動したプログラムで参照できる変数
    • つまり、環境変数は子プロセスに引き継がれる
    • 変数名に$をつけると参照できる

以下はターミナルでの実行例です。 この実行例から、シェル変数と環境変数の違いが分かりました。

# TESTというシェル変数または環境変数を削除する
unset TEST

# シェル変数TESTを設定する
TEST=hoge_fugan

# シェル変数を表示する
echo $TEST
=> hoge_fugan

# envコマンドは環境変数を一覧で表示するコマンド
# 何も出力されないので、TESTが環境変数ではないことが確認できた
env | grep TEST

# test.shを作成して、そのファイル内で、echo $TESTを書いた
cat /tmp/test.sh
=> echo $TEST

# このシェルスクリプトを実行すると何も表示されない
# つまり、シェルから起動したプログラムではシェル変数を参照できないことが示せた
zsh /tmp/test.sh

# シェル変数を消す
unset TEST

# 環境変数を設定する。
# exportを使えば、環境変数を設定できる
export TEST=hoge_fugan

# TESTが環境変数であることが分かった
env | grep TEST
=> TEST=hoge_fugan

# TESTは環境変数なので、シェルから実行するプログラムで参照できる
# そのため、echoコマンドで参照できた
zsh /tmp/test.sh
=> hoge_fugan

コマンドサーチパスとは

コマンドサーチパスとは、シェルがコマンドの実行ファイルを探しに行く際に参照するパスのことです。もっとざっくりいうと、コマンドの実行ファイルの場所を示すパスのことです。

なぜcatコマンドをシェルから実行できるのか

今までlsコマンドやcatコマンドをシェルから実行していました。それらのコマンドが実行できる理由は、それらのコマンドに対応した実行ファイルがmac上のあるディレクトリ配下に存在していて、シェルがいろんなディレクトリ配下を見て、目的の実行ファイルが存在するかを探して実行してくれるためです。

例えば、catコマンドの実行ファイルは、/bin/catに存在しています。つまり、catコマンドの実行ファイル(catファイル)は/binディレクトリ配下に存在するということです。

which cat
/bin/cat

/binディレクトリ配下のcatファイルは実行ファイル(プログラムをビルドした際に生成されるファイル)なので、シェルにcatファイルの絶対パスを指定すれば、実行できます。

/bin/cat /tmp/test.sh
=> echo $TEST

catコマンドを実行する際に、/bin/catと毎回打つのはめんどくさいです。しかし、私たちは、catコマンドを実行する際に、/bin/catなんか打ったことないです。それはなぜかというと、環境変数PATHにコマンドサーチパスの/binが既に登録されているからです。

整形前

echo $PATH
=> 
/Users/yuuki_haga/.pyenv/shims:/Users/yuuki_haga/.pyenv/bin:/opt/homebrew/opt/openssl@3/bin:/Users/yuuki_haga/.cargo/bin:/usr/local/opt/mysql@5.7/bin:/Users/yuuki_haga/.rbenv/shims:/Users/yuuki_haga/.rbenv/bin:/Users/yuuki_haga/.nodenv/shims:/Users/yuuki_haga/.nodenv/bin:/usr/local/go/bin:/Library/Frameworks/Python.framework/Versions/3.11/bin:/Users/yuuki_haga/.nodebrew/current/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin:/Library/Apple/usr/bin:/Users/yuuki_haga/.cargo/bin:/Users/yuuki_haga/exercise:/Users/yuuki_haga/original_command:~/go/bin

整形後(:を改行コードにした)

echo $PATH | tr ":" "\n"
=> 
/Users/yuuki_haga/.pyenv/shims
/Users/yuuki_haga/.pyenv/bin
/opt/homebrew/opt/openssl@3/bin
/Users/yuuki_haga/.cargo/bin
/usr/local/opt/mysql@5.7/bin
/Users/yuuki_haga/.rbenv/shims
/Users/yuuki_haga/.rbenv/bin
/Users/yuuki_haga/.nodenv/shims
/Users/yuuki_haga/.nodenv/bin
/usr/local/go/bin
/Library/Frameworks/Python.framework/Versions/3.11/bin
/Users/yuuki_haga/.nodebrew/current/bin
/usr/local/bin
/System/Cryptexes/App/usr/bin
/usr/bin
/bin
/usr/sbin
/sbin
/usr/local/go/bin
/Library/Apple/usr/bin
/Users/yuuki_haga/.cargo/bin
/Users/yuuki_haga/exercise
/Users/yuuki_haga/original_command
~/go/bin

環境変数PATHには複数のコマンドサーチパスが登録されています。それらのコマンドサーチパスは:で区切られています。コマンドを実行しろとユーザーからシェルに対して命令が来たら、シェルは環境変数PATHに登録されているコマンドサーチパスを左から順に見ていって、コマンドサーチパスのディレクトリ配下にコマンドの実行ファイルがないかを探します。そして、コマンドの実行ファイルがあったら、その実行ファイルを実行してくれます。

PATHを通すデメリット・メリット

デメリット

プログラムを実行するのに、プログラムの実行ファイルの絶対パスを指定する必要があります。めんどくさいです

メリット

プログラムをファイル名で実行できるので楽です。

実際にPATHを通してみる

自分の環境には、nodebrewというプログラムが存在します。今回、nodebrewのコマンドサーチパスをわざと消してみて、nodebrewが使えなくなったことを確認してから、PATHを通すとどうなるのかを確認してみます。

# nodebrewコマンドが使えることを確認する
nodebrew versions
=> 
nodebrew 1.0.1

Usage:
    nodebrew help                         Show this message
    nodebrew install <version>            Download and install <version> (from binary)
    nodebrew compile <version>            Download and install <version> (from source)
    nodebrew install-binary <version>     Alias of `install` (For backward compatibility)
    nodebrew uninstall <version>          Uninstall <version>
    nodebrew use <version>                Use <version>
    nodebrew list                         List installed versions
    nodebrew ls                           Alias for `list`
    nodebrew ls-remote                    List remote versions
    nodebrew ls-all                       List remote and installed versions
    nodebrew alias <key> <value>          Set alias
    nodebrew unalias <key>                Remove alias
    nodebrew clean <version> | all        Remove source file
    nodebrew selfupdate                   Update nodebrew
    nodebrew migrate-package <version>    Install global NPM packages contained in <version> to current version
    nodebrew exec <version> -- <command>  Execute <command> using specified <version>

Example:
    # install
    nodebrew install v8.9.4

    # use a specific version number
    nodebrew use v8.9.4

# nodebrewの実行ファイルがどこに存在するかを確認する
which nodebrew
/Users/yuuki_haga/.nodebrew/current/bin/nodebrew

# 実際にこのパスをシェルに渡してみて、実行できるか確認する
# 実行できた
/Users/yuuki_haga/.nodebrew/current/bin/nodebrew versions
=> 
nodebrew 1.0.1

Usage:
    nodebrew help                         Show this message
    nodebrew install <version>            Download and install <version> (from binary)
    nodebrew compile <version>            Download and install <version> (from source)
    nodebrew install-binary <version>     Alias of `install` (For backward compatibility)
    nodebrew uninstall <version>          Uninstall <version>
    nodebrew use <version>                Use <version>
    nodebrew list                         List installed versions
    nodebrew ls                           Alias for `list`
    nodebrew ls-remote                    List remote versions
    nodebrew ls-all                       List remote and installed versions
    nodebrew alias <key> <value>          Set alias
    nodebrew unalias <key>                Remove alias
    nodebrew clean <version> | all        Remove source file
    nodebrew selfupdate                   Update nodebrew
    nodebrew migrate-package <version>    Install global NPM packages contained in <version> to current version
    nodebrew exec <version> -- <command>  Execute <command> using specified <version>

Example:
    # install
    nodebrew install v8.9.4

    # use a specific version number
    nodebrew use v8.9.4

# nodebrewのコマンドサーチパスは、/Users/yuuki_haga/.nodebrew/current/bin/nodebrew
# このコマンドサーチパスがどこでPATHに通されたのかをgrepコマンドで確認する
# .zprofileファイルでPATHに通されたことを確認できた
grep -r ".nodebrew" ~/
/Users/yuuki_haga//.fig.dotfiles.bak/2022-12-28_02-28-08/.zprofile:export PATH=$HOME/.nodebrew/current/bin:$PATH

# vim ~/.zprofileを確認して、以下の行をコメントアウトした
# export PATH=$HOME/.nodebrew/current/bin:$PATH

# sourceで変更をシェルに反映する
source ~/.zprofile

# コマンドの実行結果が何も返ってこないので、nodebrewに関するコマンドサーチパスが環境変数ATHから消えた
echo $PATH | grep nodebrew

# nodebrewへのコマンドサーチパスを環境変数PATHから消したので、シェルがコマンド名から、実行ファイル名を探すことができない。
# そのため、コマンドの実行ファイル名を用いて、コマンドを実行することができない。
nodebrew
zsh: command not found: nodebrew

# nodebrewのコマンドサーチパスを環境変数PATHに登録していないので、nodebrewがどこにあるのかわからんとシェルから返答が来ている
which nodebrew
nodebrew not found

# しかし、ホスト上に実行ファイル自体は存在するので、nodebrewを実行することはできる。
/Users/yuuki_haga/.nodebrew/current/bin/nodebrew versions
=> 
nodebrew 1.0.1

Usage:
    nodebrew help                         Show this message
    nodebrew install <version>            Download and install <version> (from binary)
    nodebrew compile <version>            Download and install <version> (from source)
    nodebrew install-binary <version>     Alias of `install` (For backward compatibility)
    nodebrew uninstall <version>          Uninstall <version>
    nodebrew use <version>                Use <version>
    nodebrew list                         List installed versions
    nodebrew ls                           Alias for `list`
    nodebrew ls-remote                    List remote versions
    nodebrew ls-all                       List remote and installed versions
    nodebrew alias <key> <value>          Set alias
    nodebrew unalias <key>                Remove alias
    nodebrew clean <version> | all        Remove source file
    nodebrew selfupdate                   Update nodebrew
    nodebrew migrate-package <version>    Install global NPM packages contained in <version> to current version
    nodebrew exec <version> -- <command>  Execute <command> using specified <version>

Example:
    # install
    nodebrew install v8.9.4

    # use a specific version number
    nodebrew use v8.9.4


# だけど、毎回こんな長いパスをシェルに指定するのはめんどくさい。なのでこの実行ファイル(nodebrew)へのコマンドサーチパスをPATHに登録する(PATHに通す)
# ~/.zshrcに登録する
# nodebrewが存在するディレクトリまでをPATHに登録する
# echo $PATH:$HOME/.nodebrew/current/bin/でどんなパスになるか事前に確認しておく
export PATH=$PATH:$HOME/.nodebrew/current/bin/

# シェルに変更を反映する
source ~/.zshrc

# 無事PATHにnodebrewへのコマンドサーチパスが登録されたことを確認できた(PATHが通された)
which nodebrew
=> /Users/yuuki_haga/.nodebrew/current/bin//nodebrew

echo $PATH | grep nodebrew
=> /Users/yuuki_haga/.pyenv/shims:/Users/yuuki_haga/.pyenv/bin:/opt/homebrew/opt/openssl@3/bin:/Users/yuuki_haga/.cargo/bin:/usr/local/opt/mysql@5.7/bin:/Users/yuuki_haga/.rbenv/shims:/Users/yuuki_haga/.rbenv/bin:/Users/yuuki_haga/.nodenv/shims:/Users/yuuki_haga/.nodenv/bin:/usr/local/go/bin:/Users/yuuki_haga/.pyenv/bin:/opt/homebrew/opt/openssl@3/bin:/Users/yuuki_haga/.cargo/bin:/usr/local/opt/mysql@5.7/bin:/Users/yuuki_haga/.rbenv/shims:/Users/yuuki_haga/.rbenv/bin:/Users/yuuki_haga/.nodenv/shims:/Users/yuuki_haga/.nodenv/bin:/usr/local/go/bin:/Library/Frameworks/Python.framework/Versions/3.11/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin:/Library/Apple/usr/bin:/Users/yuuki_haga/.cargo/bin:/Users/yuuki_haga/exercise:/Users/yuuki_haga/original_command:~/go/bin:/Users/yuuki_haga/exercise:/Users/yuuki_haga/original_command:~/go/bin:/Users/yuuki_haga/.nodebrew/current/bin/

nodebrew versions
=> 
nodebrew 1.0.1

Usage:
    nodebrew help                         Show this message
    nodebrew install <version>            Download and install <version> (from binary)
    nodebrew compile <version>            Download and install <version> (from source)
    nodebrew install-binary <version>     Alias of `install` (For backward compatibility)
    nodebrew uninstall <version>          Uninstall <version>
    nodebrew use <version>                Use <version>
    nodebrew list                         List installed versions
    nodebrew ls                           Alias for `list`
    nodebrew ls-remote                    List remote versions
    nodebrew ls-all                       List remote and installed versions
    nodebrew alias <key> <value>          Set alias
    nodebrew unalias <key>                Remove alias
    nodebrew clean <version> | all        Remove source file
    nodebrew selfupdate                   Update nodebrew
    nodebrew migrate-package <version>    Install global NPM packages contained in <version> to current version
    nodebrew exec <version> -- <command>  Execute <command> using specified <version>

Example:
    # install
    nodebrew install v8.9.4

    # use a specific version number
    nodebrew use v8.9.4

実際にGoでcatコマンドもどきのコマンドを作ってみて、PATHを通して、コマンド名だけで実行できるようにする

catコマンドもどきのhenacatコマンドをGoで作りました。

github.com

cat README.md
=> # henacat% 

# catコマンドと同じように、パスを指定するとファイルの内容を出力できる
./henacat README.md
=> # henacat

# henacatというコマンドの実行ファイルは以下のディレクトリ配下に存在する
pwd
=> /Users/yuuki_haga/repos/go/cli/bin

# 実行ファイルは存在するけど、コマンドサーチパスを環境変数PATHに登録していないので、
# 当たり前だけどコマンド名だけでは、シェルは実行ファイルを見つけられない
which henacat
=> henacat not found

#コマンド名だけで実行するには、以下の3つの方法が考えられる
# 1. 既にあるコマンドサーチパスのディレクトリにプログラムを移動させる(無駄にパスを通さなくても済むが、プログラムの移動がめんどい)
# 2. プログラムがいるディレクトリのコマンドサーチパスを環境変数PATHに登録する(プログラムを動かさなくて済むが、パスを通す必要がある)
# 3. すでにPATHが通っているディレクトリに、実行ファイルへのシンボリックリンクを貼る(プログラムを動かさなくて済むし、パスを追加で通す必要もないが、シンボリックリンクを貼る必要がある)

# 1の場合
# /usr/local/go/bin配下にプログラムをコピーする
sudo cp ./henacat /usr/local/go/bin/henacat
which henacat # できた
/usr/local/go/bin/henacat

# 2の場合
export PATH=$PATH:$HOME/repos/go/cli/bin/
source ~/.zshrc
which henacat # できた
=> /Users/yuuki_haga/repos/go/cli/bin//henacat


# 3の場合
# シンボリックリンクはショートカットファイルのようなもの
# /usr/local/go/bin/配下にhenacatというシンボリックリンクを作成した
# このシンボリックリンクを実行しようとすると、go/cli/bin/henacatが実行される
# シンボリックリンクの構文は、ln -s <リンク対象のファイルまたはフォルダのパス> <作成するリンクのパス>
sudo ln -s /Users/yuuki_haga/repos/go/cli/bin/henacat /usr/local/go/bin/henacat

which henacat # できた
=> /usr/local/go/bin/henacat

ll /usr/local/go/bin
=>
total 37200
drwxr-xr-x   5 root  wheel       160  9  4 02:35 .
drwxr-xr-x  17 root  wheel       544  4 27 00:57 ..
-rwxr-xr-x   1 root  wheel  15610192  4 27 01:01 go
-rwxr-xr-x   1 root  wheel   3429776  4 27 01:01 gofmt
lrwxr-xr-x   1 root  wheel        42  9  4 02:35 henacat -> /Users/yuuki_haga/repos/go/cli/bin/henacat

終わり

PATHを通すって聞くと、環境変数PATHにコマンドサーチパスを登録するイメージでしたが、調べてみると、既存のコマンドサーチパスをうまく利用するやり方(プログラム側を動かしたり、シンボリックリンクを貼ったり)でもコマンドを実行できるので、いろんなやり方があって面白いなと思いました。

参考記事

PATHを通すとは? (Mac OS X) #初心者 - Qiita

シェル変数や環境変数を削除するコマンド #Linux - Qiita

シェル変数と環境変数の違いをコマンドラインで確認する #Linux - Qiita

シェルの基本操作法(後編3:シェル変数と環境変数) | 日経クロステック(xTECH)

シェル変数と環境変数とコマンドの先頭で指定する変数の違いについて - Linux - このすみノート

【export】Linuxで環境変数を設定・削除するコマンド | UX MILK

シェル変数、環境変数、シェル・スクリプト

【Go】コマンドライン引数を扱う。flagとos.Args

ユーザーが所属しているグループを確認する

nodebrewでNode.jsのバージョンが確認出来ない際の対処法 | ウツボウTECH

「PATHを通す」とは何か、改めてまとめてみた