「LinuCレベル1」の試験範囲に「シェルおよびスクリプト」という章がありまして、その中でシェルスクリプトを実行する場合は以下の構文が利用できます、といった説明があります。
$ . スクリプトファイル名
$ source スクリプトファイル名
$ bash スクリプトファイル名
$ ./スクリプトファイル名
.
と source
についてはシェルスクリプトの実行というよりも、 ~/.bash_profile
や ~/.profile
といったファイルの再読み込みの場合に利用する機会が多いので、「シェルスクリプトを実行している」という意識では利用していませんでした。
そこで、今回はこれらの実行方法の違いについて紐解いてみようと思います。
今回の動作確認環境
- Vagrant 2.2.14
- CentOS 8
. スクリプトファイル名
と source スクリプトファイル名
の違い
まず、 .
と source
についてですが man bash
内の SHELL BUILTIN COMMANDS
を見ると、以下のように同列に記載されていて、「現在のシェル環境でファイルを読み込んで実行する」と説明されています。このことから .
と source
に違いはなく、同等のコマンドであると考えてよさそうです。( .
および source
は alias
、 cd
などと同じくbashのビルトインコマンドなので man builtins
でもマニュアルが参照可能です。)
SHELL BUILTIN COMMANDS
〜省略〜
. filename [arguments]
source filename [arguments]
Read and execute commands from filename in the current shell
environment and return the exit status of the last command exe-
cuted from filename. If filename does not contain a slash,
filenames in PATH are used to find the directory containing
filename. The file searched for in PATH need not be executable.
man bash
内で言及されていることから、bashの場合は source
が利用可能だとわかりますが、その他のシェルでは使えないこともあります。
例として、CentOS8とUbuntu21.10 (Impish Indri)の動作を比較してみます。
CentOS8の場合
CentOSのバージョンを確認。
$ cat /etc/os-release |grep '^VERSION='
VERSION="8 (Core)"
現在のシェルがbashであることを確認。
$ echo $0
-bash
source
を実行すると ~/.bash_profile
を読み込むことが可能です。
$ source ~/.bash_profile
シェルを /usr/bin/sh
に切り替えて、現在のシェルがshであることを確認します。
$ /usr/bin/sh
$ echo $0
/usr/bin/sh
source
を実行しても問題なく読み込めます。
$ source ~/.bash_profile
理由は /usr/bin/sh
が bash
へのシンボリックリンクになっているため source
が利用可能というわけです。
$ ls -l /usr/bin/sh
lrwxrwxrwx. 1 root root 4 May 11 2019 /usr/bin/sh -> bash
ちなみに /usr/bin/sh
を利用している状態で .
を実行しても ~/.bash_profile
が問題なく読み込まれます。
$ echo $0
/usr/bin/sh
$ . ~/.bash_profile
Ubuntu21.10 (Impish Indri)の場合
Ubuntuのバージョンを確認。
$ cat /etc/os-release |grep '^VERSION='
VERSION="21.10 (Impish Indri)"
現在のシェルがbashであることを確認。
$ echo $0
-bash
source
を実行すると ~/.profile
を読み込むことが可能です。
$ source ~/.profile
シェルを /usr/bin/sh
に切り替えて、現在のシェルがshであることを確認します。
$ /usr/bin/sh
$ echo $0
/usr/bin/sh
source
を実行するとコマンドが見つからないというエラーが発生します。
$ source ~/.profile
/usr/bin/sh: 1: source: not found
理由は /usr/bin/sh
が dash
へのシンボリックリンクになっていて dash
では source
が利用できないからです。
$ ls -l /usr/bin/sh
lrwxrwxrwx 1 root root 4 Jul 1 17:34 /usr/bin/sh -> dash
/usr/bin/sh
を利用している状態で source
は利用できませんでしたが .
を実行すると ~/.profile
が読み込まれます。
$ echo $0
/usr/bin/sh
$ . ~/.profile
.
と source
は同等のコマンドであること、利用するシェルによっては source
が利用できない場合があることを踏まえて、以降の動作検証では .
を利用することとします。
bash スクリプトファイル名
と ./スクリプトファイル名
の違い
./スクリプトファイル名
という形式でスクリプトを実行する場合に shebang
の記述が必要だということは以下の記事でまとめています。
また、 shebang
に #!/bin/bash
と記述している場合は /bin/bash スクリプトファイル名
のように呼び出されているということは、以下の記事でまとめています。
つまり、 bash スクリプトファイル名
と ./スクリプトファイル名
は同等であると言えます。このことを踏まえ、以降の動作検証では bash スクリプトファイル名
を利用することとします。
. スクリプトファイル名
と bash スクリプトファイル名
の違い
さて、ここまでを内容をまとめると、どうやら以下の2種類の実行方法の違いを調べれば source スクリプトファイル名
と ./スクリプトファイル名
の動作についても把握できたと考えてよさそうです。
$ . スクリプトファイル名
$ bash スクリプトファイル名
プロセスIDについて
. スクリプトファイル名
は man bash
内で「現在のシェル環境でファイルを読み込んで実行する」と説明されていましたが、 bash スクリプトファイル名
については man bash
内の COMMAND EXECUTION
にて以下のように記載されています。
COMMAND EXECUTION
〜省略〜
If this execution fails because the file is not in executable format,
and the file is not a directory, it is assumed to be a shell script, a
file containing shell commands. A subshell is spawned to execute it.
This subshell reinitializes itself, so that the effect is as if a new
shell had been invoked to handle the script, with the exception that
the locations of commands remembered by the parent (see hash below
under SHELL BUILTIN COMMANDS) are retained by the child.
重要なポイントとしては「スクリプトファイルを実行する毎に新たなサブシェル(子プロセス)が生成される」というところかと思います。
文章による説明ではわかりにくいと思うので、プロセスIDに着目してそれぞれの動作の違いを確認してみようと思います。
まずはスクリプト内でプロセスIDを表示する show_pid.sh
を準備します。
$ echo 'echo "PID: $$"' > show_pid.sh
現在のシェルのプロセスIDを確認すると 2574
と表示されました。
$ echo $$
2574
. show_pid.sh
で呼び出すと「現在のシェル環境でファイルを読み込んで実行する」ので現在のシェルのプロセスIDと同じ値の 2574
が表示されます。
$ . show_pid.sh
PID: 2574
この操作を繰り返すと、何度実行しても同じプロセスIDが返ってくることがわかります。
$ . show_pid.sh
PID: 2574
$ . show_pid.sh
PID: 2574
$ . show_pid.sh
PID: 2574
bash show_pid.sh
で呼び出すと「実行する毎に新たなサブシェル(子プロセス)が生成される」ので現在のシェルのプロセスIDとは異なる値の 2601
が表示されました。
$ bash show_pid.sh
PID: 2601
この操作を繰り返すと、 実行する毎に新たなプロセスIDが生成されていることがわかります。
$ bash show_pid.sh
PID: 2603
$ bash show_pid.sh
PID: 2604
$ bash show_pid.sh
PID: 2605
プロセスIDに着目した .
と bash
の動作の違いが理解できたのではないでしょうか。
シェル変数と環境変数について
シェル変数は子プロセスに変数の内容が引き継がれませんが、環境変数は子プロセスにも内容が引き継がれます。次はこの動作について見ていきましょう。
まずは現在のシェル環境でシェル変数 $SHELL_VARIABLE
を定義して、シェル変数を一覧表示する set
コマンドと変数の内容を表示する echo
コマンドで正しく定義されているか確認します。
$ SHELL_VARIABLE='This is shell variable!'
$ set |grep SHELL_VARIABLE
SHELL_VARIABLE='This is shell variable!'
$ echo $SHELL_VARIABLE
This is shell variable!
次に、環境変数 $ENV_VARIABLE
を定義して、環境変数を一覧表示する env
コマンドと 変数の内容を表示する echo
コマンドで正しく定義されているか確認します。
$ ENV_VARIABLE='This is environment variable!'; export ENV_VARIABLE
$ env |grep ENV_VARIABLE
ENV_VARIABLE=This is environment variable!
$ echo $ENV_VARIABLE
This is environment variable!
シェル変数 $SHELL_VARIABLE
と環境変数 $ENV_VARIABLE
の内容を表示する show_variables.sh
を準備します。
$ cat show_variables.sh
echo '$SHELL_VARIABLE: '$SHELL_VARIABLE
echo '$ENV_VARIABLE: '$ENV_VARIABLE
. show_variables.sh
で実行すると、同一プロセスのためシェル変数、環境変数ともに表示されます。
$ . show_variables.sh
$SHELL_VARIABLE: This is shell variable!
$ENV_VARIABLE: This is environment variable!
bash show_variables.sh
で実行すると子プロセスで起動するため、シェル変数の値は引き継がれず環境変数のみが引き継がれて表示されます。
$ bash show_variables.sh
$SHELL_VARIABLE:
$ENV_VARIABLE: This is environment variable!
現在動作しているシェルへの影響について
最後は、スクリプト内で実行した操作が現在動作しているシェルに対して影響を与えるかについてです。
ディレクトリ移動編
以下のようなスクリプト内でディレクトリ移動を行う change_directory.sh
を準備します。
$ cat change_directory.sh
cd /tmp
. スクリプトファイル名
の場合、同一プロセスのため、現在動作しているシェルのディレクトリが変わりました。
$ pwd
/home/vagrant
$ . change_directory.sh
$ pwd
/tmp
bash スクリプトファイル名
の場合、サブシェル(子プロセス)内でのみディレクトリ移動を行っているため、現在のシェルには影響を与えません。
$ pwd
/home/vagrant
$ bash change_directory.sh
$ pwd
/home/vagrant
環境変数上書き編
~/.bash_profile
や ~/.profile
などの設定内容を書き換えた際に、なぜ .
(または source
)を利用するのかという理由についても、検証しておかないとですね。
現在のシェルで先ほど定義しておいた環境変数を echo
で確認します。
$ echo $ENV_VARIABLE
This is environment variable!
定義済みの環境変数を上書きして表示するスクリプトを準備します。
$ cat overwrite_variables.sh
ENV_VARIABLE='overwrite environment variable'
echo '$ENV_VARIABLE: '$ENV_VARIABLE
子プロセスで実行される bash
の方から試してみましょう。子プロセス内では上書きに成功しているようです。
$ bash overwrite_variables.sh
$ENV_VARIABLE: overwrite environment variable
現在のシェルで環境変数の値を確認するとスクリプト実行による影響はなく元の値を維持していることがわかります。
$ echo $ENV_VARIABLE
This is environment variable!
それでは .
で試してみましょう。こちらも上書きに成功しているようです。
$ . overwrite_variables.sh
$ENV_VARIABLE: overwrite environment variable
現在のシェルで環境変数の値を確認すると、同一プロセスIDで動作しているため、現在のシェルの環境でも上書きされていることがわかります。
$ echo $ENV_VARIABLE
overwrite environment variable
これらの動作結果から環境変数の値を ~/.bash_profile
などに記述して現在のシェルに反映させたい場合に、なぜ .
(または source
)を利用する理由がわかりましね。
答えは .
(または source
) は同一プロセスIDで実行されるので、スクリプト内の操作が現在のシェルに影響を与えるため、です。
まとめ
シェルスクリプトの実行方法の違いについて色々と試した結果を表にまとめてみました。
これによりそれぞれの実行方法の特徴が見えてきたんじゃないでしょうか。
. スクリプトファイル名 | bash スクリプトファイル名 | |
---|---|---|
同等のコマンドは | source スクリプトファイル名 ※利用できないシェルがあるので注意が必要 |
./スクリプトファイル名 |
現在のシェルのプロセスIDと | 同じプロセスIDで実行 | 異なるプロセスIDで実行 ※子プロセスのIDが生成される |
シェル変数 | 引き継がれる | 引き継がれない |
環境変数 | 引き継がれる | 引き継がれる |
スクリプト内の操作が現在のシェルに影響を | 与える | 与えない |
おまけ
.
(または source
)を「ファイルを読み込んで設定を反映するためのもの」のような理解をしているとこのような悲劇が起こりますのでご注意ください。。。
また、操作ミスによるこういった悲劇も避けたいものです。。。
参考URL
- bashの似てて紛らわしいもの '.' と 'source' について
- shとsourceの違い
- シェルスクリプトを実行する2種類の方法とその違い
- 【 source 】コマンド/【 . 】コマンド――シェルの設定を即座に反映させる
- シェルスクリプトの実行方法4つの違いメモ【Linux】
- 【 source 】【.】 ファイルからコマンドを読み込んで現在のシェル環境で実行 【 Linuxコマンドまとめ 】
- sourceコマンドが.**rcをリフレッシュするコマンドじゃないと知る
- Linux — source コマンドは何をしているのか > 実は環境をリロードするためのものではない
- UbuntuのMakefileでsourceコマンドを実行すると「source: コマンドが見つかりませんでした」と出る
- source ~/.bash_profile や source venv/bin/activate と何度も入力したことのある人へ
- source コマンドでシェルの設定を反映する
- Crontab で source ~/.bashrc や bash -l を使うと死を招く
- source(.)の使い方
- '. test.sh'と'sh test.sh'と'./test.sh'の違い