Mac OS Xでも同じように調べてみると、/usr/bin/cdと実体が同じファイルが15個あることが分かる。
% pwd /usr/bin % ls -il cd 3475914 -r-xr-xr-x 15 root wheel 190 7 21 00:55 cd % ls -il | grep 3475914 3475914 -r-xr-xr-x 15 root wheel 190 7 21 00:55 alias 3475914 -r-xr-xr-x 15 root wheel 190 7 21 00:55 bg 3475914 -r-xr-xr-x 15 root wheel 190 7 21 00:55 cd 3475914 -r-xr-xr-x 15 root wheel 190 7 21 00:55 command 3475914 -r-xr-xr-x 15 root wheel 190 7 21 00:55 fc 3475914 -r-xr-xr-x 15 root wheel 190 7 21 00:55 fg 3475914 -r-xr-xr-x 15 root wheel 190 7 21 00:55 getopts 3475914 -r-xr-xr-x 15 root wheel 190 7 21 00:55 hash 3475914 -r-xr-xr-x 15 root wheel 190 7 21 00:55 jobs 3475914 -r-xr-xr-x 15 root wheel 190 7 21 00:55 read 3475914 -r-xr-xr-x 15 root wheel 190 7 21 00:55 type 3475914 -r-xr-xr-x 15 root wheel 190 7 21 00:55 ulimit 3475914 -r-xr-xr-x 15 root wheel 190 7 21 00:55 umask 3475914 -r-xr-xr-x 15 root wheel 190 7 21 00:55 unalias 3475914 -r-xr-xr-x 15 root wheel 190 7 21 00:55 wait %
中身もよく似ている。よく似ているというか、この部分はFreeBSDからMac OS Xへマージして使っているので、ほとんど同じだ。ただし、Mac OS Xで使っている大文字と小文字を区別しないファイルシステムでも問題なく動作するように変更が加わっている。Mac OS Xの/usr/bin/cdの中身は次のとおり。コメントにFreeBSDの文字が見える通り、このファイルはFreeBSDから移植したものを変更したものとなっている。
#!/bin/sh # $FreeBSD: src/usr.bin/alias/generic.sh,v 1.2 2005/10/24 22:32:19 cperciva Exp $ # This file is in the public domain. builtin `echo ${0##*/} | tr \[:upper:] \[:lower:]` ${1+"$@"}
Solaris 11でも同じことを確認できる。/usr/bin/cdと実体が同じファイルが18個もあるのだ。PC-BSD/FreeBSD/Mac OS Xよりも3つ(test、print、kill)多い。
$ pwd /usr/bin $ ls -il cd 87860 -r-xr-xr-x 18 root bin 18224 10月 21日 07:52 cd $ ls -il | grep 87860 87860 -r-xr-xr-x 18 root bin 18224 10月 21日 07:52 alias 87860 -r-xr-xr-x 18 root bin 18224 10月 21日 07:52 bg 87860 -r-xr-xr-x 18 root bin 18224 10月 21日 07:52 cd 87860 -r-xr-xr-x 18 root bin 18224 10月 21日 07:52 command 87860 -r-xr-xr-x 18 root bin 18224 10月 21日 07:52 fc 87860 -r-xr-xr-x 18 root bin 18224 10月 21日 07:52 fg 87860 -r-xr-xr-x 18 root bin 18224 10月 21日 07:52 getopts 87860 -r-xr-xr-x 18 root bin 18224 10月 21日 07:52 hash 87860 -r-xr-xr-x 18 root bin 18224 10月 21日 07:52 jobs 87860 -r-xr-xr-x 18 root bin 18224 10月 21日 07:52 kill 87860 -r-xr-xr-x 18 root bin 18224 10月 21日 07:52 print 87860 -r-xr-xr-x 18 root bin 18224 10月 21日 07:52 read 87860 -r-xr-xr-x 18 root bin 18224 10月 21日 07:52 test 87860 -r-xr-xr-x 18 root bin 18224 10月 21日 07:52 type 87860 -r-xr-xr-x 18 root bin 18224 10月 21日 07:52 ulimit 87860 -r-xr-xr-x 18 root bin 18224 10月 21日 07:52 umask 87860 -r-xr-xr-x 18 root bin 18224 10月 21日 07:52 unalias 87860 -r-xr-xr-x 18 root bin 18224 10月 21日 07:52 wait $
Solaris 11の場合はバイナリファイルになっているが、基本的にやっていることは同じだ。組み込みコマンドがlibcmdにまとめられているので、そちらを直接呼び出すか、またはシェルへ処理を引き渡すようになっている。Solaris 10以前はFreeBSDやMac OS Xのようなシェルスクリプトが使われている。
なぜこのような「不思議」なシェルスクリプトがシステムに存在しているのか? コミットログ「/head/usr.bin/alias/generic.sh - Revision 100200」を見ると分かりやすい。「A little bit more thought has resulted in a generic script which can implement any of the useless POSIX-required ``regular shell builtin'' utilities, saving one frag and one inode each.」使いものになるかどうかは別として、POSIX.1が要求する標準組み込みユーティリティの機能を実現するためにこの機能を導入しているのだ。
標準組み込みユーティリティにどのような要求があるかは、「The Open Group Base Specifications Issue 7 - IEEE Std 1003.1-2008」の次の部分の説明を読むと分かるだろう。
詳しい説明は別の機会に譲るが、POSIX.1ではシェルスクリプトのユーティリティを標準ユーティリティ(レギュラー組み込みユーティリティを含む)と特殊組み込みユーティリティに分けて扱っている。POSIX.1に表記があるものであれば、例えば「break、:、continue、.、eval、exec、exit、export、readonly、return、set、shift、times、trap、unset」などが特殊組み込みユーティリティとなる。
これ以外のユーティリティは、コマンドが存在するか組み込まれているかにかかわらず標準ユーティリティとなる。標準ユーティリティはexec(3)ファミリから利用できる必要があるほか、その動作を必要とするほかの標準ユーティリティから利用できなければならないという説明がある。実行ファイルが存在するコマンドであればこの条件を満たせるが、組み込みコマンドの場合は、ひと工夫する必要がある。
シェルスクリプトの実装では実行速度を少しでも上げるために、よく使うユーティリティは組み込みで実装することが多い。例えばPOSIX.1なら「alias、bg、cd、command、false、fc、fg、getopts、jobs、kill、newgrp、pwd、read、true、umask、unalias、wait」などがレギュラー組み込みユーティリティとなっており、事実、組み込みコマンドとして実装されることが多い。
当然組み込みコマンドはコマンド単体でファイルが存在するということがない。このままではexec(3)から呼ぶことはできない。しかしPOSIX.1はそうした組み込みコマンドに対しても、ファイルが存在するコマンドのようにexec(3)ライブラリから利用できる必要があるとしている。
/usr/bin/cdのようなシェルスクリプトは、まさにこのために存在する。この組み込みコマンドの実行へ置き換えるシェルスクリプトが、ファイルの実体として機能するためexec(3)から利用できるようになる。意味があるかどうかは別として、POSIX.1はそう動作することを要求している。例えばシェルであれば、シェルから「exec cd」のように実行したら、それが問題なく処理されることを求めている。/usr/bin/cdはこうした要望を実現するための合理的で分かりやすい実装というわけだ。
/usr/bin/cdは短いシェルスクリプトだが、その短いスクリプトの中に、よく使われるテクニックを収録しており、仕組みと仕掛けが面白い。最初に取り上げるには良い対象だろう。短いが適度に難しく、そして意味が分かりにくいので調査を続けるモチベーションにもつながる。最終的にはPOSIX.1にたどり着くので、POSIX.1を読み始める取っ掛かりを得やすい部分ではないかと思う。
こうしたケースのほかに、組み込みコマンドをコマンドに対応させるテクニックを使うことはあまりないが、システムに理解できないシェルスクリプトがあるというのは気分のよくないものだ。こういう疑問は最初につぶしてしまった方が精神衛生上好ましい。
本稿執筆段階で最新のPC-BSD 9.0はRC3だが、RC3からはシステムソースコードとPorts Collectionがインストール時にまとめてインストールできなかった。このため補足ということで、後からPorts Collectionやシステムソースコードをインストールする方法を紹介しておく。まず、Ports Collectionはroot権限で次のように作業して最新版を取得できる。
portsnap fetch extract update
いったん取得したら、「portsnap fetch update」を実行すれば最新版に更新できる。システムのソースコードは次のようにsvn(1)コマンドを実行すれば取得できる。
svn co http://svn.freebsd.org/base/releng/9.0 /usr/src
Gitで取得する方法もある。Gitを使うときは「Git - FreeBSD Wiki」に説明がまとまっているので参考にしてほしい。
Copyright © ITmedia, Inc. All Rights Reserved.