シェルは「コマンドを実行する」というのが最も基本的な使い方であり、シェル関数や組み込みコマンドなども実行することができる。それぞれ特徴を簡単にまとめると次のようになる。
種類 | 内容 |
---|---|
シェル関数 | シェルの提供している関数機能を使って作成された関数。シェルスクリプトでもインタラクティブシェルでも利用可能 |
組み込みコマンド | シェル内部に実装されたコマンド。別プロセスとして実行するということはなく、そのシェルのプロセス内で実行される |
コマンド | ファイルシステム上に存在している実行ファイル |
シェルは、これらを次の順序で優先的に解釈していく。
- シェル関数
- 組み込みコマンド
- コマンド
コマンドは指定されたコマンドにパスの区切りを意味するスラッシュが含まれているかどうかで解釈の順序が変わる。より正確に言うと、次の順序で実行が試みられる。
- シェル関数
- 組み込みコマンド
- スラッシュを含むコマンド
- スラッシュを含まないコマンド
次にそれぞれどのような特徴があるのか見ていこう。
シェル関数
シェルではシェル関数が最も優先的に評価される。例えば組み込みコマンドやコマンドで同名のものがあった場合、シェル関数が最初に実行されることになる。シェル関数を定義することで、既存の組み込みコマンドやコマンドを上書きするようなイメージでシェル関数を優先的に実行させることができるわけだ。
# type test
test is a shell builtin
# test()
> {
> echo test
> }
# type test
test is a function
test ()
{
echo test
}
#
シェルスクリプトでは引数を参照する方法として位置パラメータが使われる。「$1」が1つ目の引数、「$2」が2つ目の引数、「$3」が3つ目の引数といった具合になっている。シェル関数では、この位置パラメータの値がスコープとして別の扱いになる点に注意する必要がある。
例えば、次のようなシェルスクリプト「func_test.sh」を用意したとする。
#!/bin/sh
echo \$0: $0
echo \$1: $1
echo \$2: $2
echo \$3: $3
echo \$4: $4
echo ----
func()
{
echo \$0: $0
echo \$1: $1
echo \$2: $2
echo \$3: $3
echo \$4: $4
echo ----
}
func a b c d e
echo \$0: $0
echo \$1: $1
echo \$2: $2
echo \$3: $3
echo \$4: $4
echo ----
実行すると次のようになる。
# ./func_test.sh A B C D E
$0: ./func_test.sh
$1: A
$2: B
$3: C
$4: D
----
$0: ./func_test.sh
$1: a
$2: b
$3: c
$4: d
----
$0: ./func_test.sh
$1: A
$2: B
$3: C
$4: D
----
#
位置パラメータの値が、シェル関数の中と外とでは別の値になっていることが確認できる。シェル関数の中の位置パラメータはシェル関数に指定された引数の値への参照となるためだ。なお、「$0」は位置パラメータではなくシェルスクリプト名が収められており、この値はシェル関数の外でも中でも変わらない。
組み込みコマンド
組み込みコマンドはシェルの内部に実装されている処理だ。シェル関数はシェルとして記述された関数を読み込んでメモリ上に保持された実行コードで、組み込みコマンドは初めからシェルのバイナリコードに埋め込まれた処理となっている。
次のコードを見るとわかるように、「test」は組み込みコマンド、/bin/testはファイルシステム上のコマンドだ。実際、この2つは同じソースコードからビルドされているため提供機能は同じなのだが、新しいプロセスで動作するか、シェルのプロセスで実行されるのか、という違いがある。
# type test
test is a shell builtin
# test -s /dev/null || echo /dev/null is empty.
/dev/null is empty.
#
# type /bin/test
/bin/test is /bin/test
# /bin/test -s /dev/null || echo /dev/null is empty.
/dev/null is empty.
#
上記サンプルは動作としてはほとんど同じだが、testがシェルプロセスで実行されるのに対し、/bin/testは新しく生成されたプロセスとして実行されるという違いがある。
シェル関数や組み込みコマンドはシェルプロセスの内部で実行され、コマンドは別のプロセスとして生成されてから実行される。これは初めのうちは気にならないかもしれないが、いずれ仕組みとして理解しておいたほうがよいときがやってくる。そういった違いがあるということは、この段階で理解しておきたい。
スラッシュを含むコマンド
次に評価されるのはスラッシュを含むコマンドだ。スラッシュを含んだコマンドは相対パスにせよ絶対パスによせ、パスが指定されたコマンドだと解釈され、そのまま実行される。
スラッシュを含まないコマンド
スラッシュを含まないコマンドが指定された場合、環境変数PATHに設定されたディレクトリ以下を調べ、同じ名前のファイルが存在した場合に、そのコマンドが実行されることになる。例えば次の実行サンプルの場合、「dig」というコマンドは/usr/local/bin/digが本体であり、環境変数PATHに/usr/local/bin/が含まれていることから、該当する/usr/local/bin/digが実行されることがわかる。
$ echo $PATH
/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
$ type dig
dig is /usr/local/bin/dig
$ ls -l /sbin/dig
ls: /sbin/dig: そのようなファイルまたはディレクトリはありません
$ ls -l /bin/dig
ls: /bin/dig: そのようなファイルまたはディレクトリはありません
$ ls -l /usr/sbin/dig
ls: /usr/sbin/dig: そのようなファイルまたはディレクトリはありません
$ ls -l /usr/bin/dig
ls: /usr/bin/dig: そのようなファイルまたはディレクトリはありません
$ ls -l /usr/local/sbin/dig
ls: /usr/local/sbin/dig: そのようなファイルまたはディレクトリはありません
$ ls -l /usr/local/bin/dig
-r-xr-xr-x 1 root wheel 1986248 6月 24 2018 /usr/local/bin/dig
$
$ dig www.example.org
; <<>> DiG 9.9.12 <<>> www.example.org
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 16816
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; MBZ: 0x0005, udp: 512
;; QUESTION SECTION:
;www.example.org. IN A
;; ANSWER SECTION:
www.example.org. 5 IN A 93.184.216.34
;; Query time: 9 msec
;; SERVER: 192.168.185.2#53(192.168.185.2)
;; WHEN: 月 7月 01 14:09:08 JST 2019
;; MSG SIZE rcvd: 60
$
環境変数PATHに設定するディレクトリはコロン区切りで指定することができ、先頭から順に検索が実施される。
終了ステータス
シェル関数や組み込みコマンド、コマンドには終了ステータスがある。どういった値が終了ステータスになるかはコマンドによって異なるのだが、基本的には成功した場合には「0」が終了ステータスとなり、失敗した場合には0以外の値が終了ステータスになる。どの値が使われるはそれぞれのコマンドのオンラインマニュアルを参照されたい。
ただし、コマンドがシグナルで終了した場合には終了ステータスが128よりも大きくなるように実装されている。この値をチェックすることで、コマンドが自主的に終了したのか、シグナルで終了したのかを判定することができる。
* * *
今回解説したように、シェルはシェル関数、組み込みコマンド、コマンドに対して優先順位を定めており、シェル関数が最も優先される仕組みになっている。この辺りは普段あまり気にする必要はないが、そういったルールに従って動作しているということは覚えておこう。