whichコマンドの実体


アニメ『ブレイブウィッチーズ』
なんだか『ストライクウィッチーズ』に似てるなあ、似せたものを作るのって難しいのでは、と思っていたら、どうも『ストライクウィッチーズ』の新作のようです。タイトルが違うだけした。『ストライクウィッチーズ』では、501でしたが、今回は、第502統合戦闘航空団です。このアニメシリーズのウィッチは、魔法を使うときに、動物の耳としっぽが生えます。重要な人には重要なポイントなのでしょうね。

今回はウィッチ繋がりで、whichコマンドのソースでも読んでみましょう。

パッケージは、たぶんcoreutilsでしょうか。ソースを取得します。ソースは今後すぐに参照できるように決まったディレクトリ、/usr/srcの下に入れておきます。シンボリックリンクも貼っておきます。

~$ su -
# cd /usr/src
# apt-get source coreutils
(省略)
# ln -s coreutils-8.23 coreutils
# exit

findでwhichを探してみましょう。

~$ find -L /usr/src/coreutils -name '*which*'

あれ、ありません。ではちゃんと探してみましょう。まずは、which自体をwhichして、パスを得ます。

~$ which which
/usr/bin/which
~$ 

次にパッケージの検索です。

~$ apt-file search /usr/bin/which
debian-goodies: /usr/bin/which-pkg-broke
debianutils: /usr/bin/which
emboss: /usr/bin/whichdb
whichman: /usr/bin/whichman
~$ 

どうやらwhichが格納されているのは、debianutilsのようです。
debianutilsのソースを取得します。例によって、/usr/src/の下に格納してシンボリックリンクを貼っておきます。

~$ su -
# cd /usr/src
# apt-get source debianutils
(省略)
# ln -s debianutils-4.4 debianutils
# exit

findでwhichのソースを探します。

~$ find -L /usr/src/debianutils -name '*which*'
/usr/src/debianutils/po4a/pl/which.1
/usr/src/debianutils/po4a/ja/which.1
/usr/src/debianutils/po4a/de/which.1
/usr/src/debianutils/po4a/it/which.1
/usr/src/debianutils/po4a/es/which.1
/usr/src/debianutils/po4a/sl/which.1
/usr/src/debianutils/po4a/fr/which.1.fr.add
/usr/src/debianutils/po4a/fr/which.1
/usr/src/debianutils/which
/usr/src/debianutils/which.1
~$ 

あれれ、おかしいです。C言語のソースが見当たりません。
もしかして、whichがソースなのでしょうか。fileコマンドでチェックします。

~$ !! | xargs file
find -L /usr/src/debianutils -name '*which*' | xargs file
/usr/src/debianutils/po4a/pl/which.1:        troff or preprocessor input, UTF-8 Unicode text
/usr/src/debianutils/po4a/ja/which.1:        troff or preprocessor input, UTF-8 Unicode text
/usr/src/debianutils/po4a/de/which.1:        troff or preprocessor input, UTF-8 Unicode text
/usr/src/debianutils/po4a/it/which.1:        troff or preprocessor input, UTF-8 Unicode text
/usr/src/debianutils/po4a/es/which.1:        troff or preprocessor input, UTF-8 Unicode text
/usr/src/debianutils/po4a/sl/which.1:        troff or preprocessor input, UTF-8 Unicode text
/usr/src/debianutils/po4a/fr/which.1.fr.add: troff or preprocessor input, UTF-8 Unicode text
/usr/src/debianutils/po4a/fr/which.1:        troff or preprocessor input, UTF-8 Unicode text
/usr/src/debianutils/which:                  POSIX shell script, ASCII text executable
/usr/src/debianutils/which.1:                troff or preprocessor input, ASCII text
~$ 

下から2行目を見てみると、shell scriptのようです。もしかして、/usr/bin/whichもテキストファイルなのでしょうか。確認してみます。

~$ file /usr/bin/which
/usr/bin/which: symbolic link to /bin/which
~$ file /bin/which
/bin/which: POSIX shell script, ASCII text executable
~$ 

やはり同じくテキストファイルでした。headしてみます。wcで行数もみてみましょう。

~$ head /bin/which
#! /bin/sh
set -ef

if test -n "$KSH_VERSION"; then
	puts() {
		print -r -- "$*"
	}
else
	puts() {
		printf '%s\n' "$*"
~$ wc -l !$
wc -l /bin/which
63 /bin/which
~$ 

行数も少なく、headを見る限り簡潔に書かれているようですので、この際全文見てみましょう。

~$ cat -n /bin/which
     1	#! /bin/sh
     2	set -ef
     3	
     4	if test -n "$KSH_VERSION"; then
     5		puts() {
     6			print -r -- "$*"
     7		}
     8	else
     9		puts() {
    10			printf '%s\n' "$*"
    11		}
    12	fi
    13	
    14	ALLMATCHES=0
    15	
    16	while getopts a whichopts
    17	do
    18	        case "$whichopts" in
    19	                a) ALLMATCHES=1 ;;
    20	                ?) puts "Usage: $0 [-a] args"; exit 2 ;;
    21	        esac
    22	done
    23	shift $(($OPTIND - 1))
    24	
    25	if [ "$#" -eq 0 ]; then
    26	 ALLRET=1
    27	else
    28	 ALLRET=0
    29	fi
    30	case $PATH in
    31		(*[!:]:) PATH="$PATH:" ;;
    32	esac
    33	for PROGRAM in "$@"; do
    34	 RET=1
    35	 IFS_SAVE="$IFS"
    36	 IFS=:
    37	 case $PROGRAM in
    38	  */*)
    39	   if [ -f "$PROGRAM" ] && [ -x "$PROGRAM" ]; then
    40	    puts "$PROGRAM"
    41	    RET=0
    42	   fi
    43	   ;;
    44	  *)
    45	   for ELEMENT in $PATH; do
    46	    if [ -z "$ELEMENT" ]; then
    47	     ELEMENT=.
    48	    fi
    49	    if [ -f "$ELEMENT/$PROGRAM" ] && [ -x "$ELEMENT/$PROGRAM" ]; then
    50	     puts "$ELEMENT/$PROGRAM"
    51	     RET=0
    52	     [ "$ALLMATCHES" -eq 1 ] || break
    53	    fi
    54	   done
    55	   ;;
    56	 esac
    57	 IFS="$IFS_SAVE"
    58	 if [ "$RET" -ne 0 ]; then
    59	  ALLRET=1
    60	 fi
    61	done
    62	
    63	exit "$ALLRET"

このスクリプトには、ライセンス表記がないんですねえ。
PATH中のディレクトリの中に、引数で指定したコマンドのファイルが見つかるかチェックするスクリプトとなっています。PATHは、ご存じのとおり:(コロン)で区切りますが、36行目で、IFSを:(コロン)に設定して、デリミタを:(コロン)とし、ELEMENTにPATH中の各ディレクトリを格納して、

「ELEMENT/引数で指定したコマンド」

のパスのファイルがtestコマンド([])で見つかるかチェックしています。チェックが済んだら、IFSをIFS_SAVEに保存した値で元に戻していますね。

端折って書くとこんな感じになるのでしょうか。作ってみました。

~$ cat -n mywhich.sh
     1	for i in `tr : ' ' <<< $PATH`;do 
     2		if [ -f $i/$1 ];then
     3			echo $i/$1
     4		fi
     5	done
~$ 

使ってみます。

~$ chmod 755 mywhich.sh
~$ ./mywhich.sh which
/usr/bin/which
/bin/which

シンボリックリンクと実体のパスが表示されましたね。

whichコマンドと表示を比べてみましょう。

~$ which which
/usr/bin/which
~$ 

なるほど、本物のwhichコマンドは、シンボリックリンクを辿らないようです。
なかなか似せたものを作るのは難しいですね。

コメント

  1. […] 「whichコマンドの実体」ではwhichコマンドのスクリプトを見てみましたが、このの他にもスクリプトで作られているコマンドがあるのでしょうか。確認してみましょう。 […]

タイトルとURLをコピーしました