envコマンドについて

pythonスクリプトの1行目に#!/usr/bin/env pythonとかを書くが,envコマンドが何をやっているのかいまいちわかっていなかった.のでメモする.

まず,envコマンドをそのまま実行すると,環境変数の一覧が見れる.

$ env
XDG_SESSION_ID=214
HOSTNAME=localhost.localdomain
SELINUX_ROLE_REQUESTED=
TERM=xterm
...(中略)...
OLDPWD=/home

また,envの後に引数としてコマンドを指定すると,${PATH}を頭から順に見ていき,指定したコマンドを実行したところでそいつを実行する.(ようだ)
そのため,#!/usr/bin/env pythonが頭に書いてあるスクリプトは,shell上でpythonの引数としてではなく直接実行 (./foo.pyのように) されたとき,ユーザーの${PATH}環境変数を頭から見て最初に見つかったパスのpythonからスクリプトが実行されるようになっている.
これは環境によって/usr/bin/pythonだったり/usr/local/bin/pythonだったりとpythonの居場所が違うことがあるので,どこにpythonがいてもパスさえ通っていればちゃんとスクリプトが動作するようにしているのが#!/usr/bin/env pythonの意味になるようだ.

env COMMANDの挙動の実験をしてみる.

$ cat foo.sh
#!/bin/bash

set -u

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

echo "This function\'s location is ${DIR}."

このfoo.sh/home/vagrant/home/vagrant/tmpの両方に置いた.これらが呼ばれるとそのスクリプトが存在しているフルパスを返す.

この状態で以下の実験を行った.

$ echo ${PATH}
/sbin:/bin:/usr/sbin:/usr/bin
$ # パスを追加
$ export PATH=/home/vagrant:/home/vagrant/tmp:${PATH}
$ echo ${PATH}
/home/vagrant:/home/vagrant/tmp:/sbin:/bin:/usr/sbin:/usr/bin
$ env foo.sh
This function\'s location is /home/vagrant.
$ # 追加したパスをワンライナーで削除 (関数化したほうがいいかも)
$ P=/home/vagrant; export PATH=$(echo ${PATH} | awk -v RS=: -v ORS=: '$0 != "'${P}'"' | sed 's/:$//')
$ P=/home/vagrant/tmp; export PATH=$(echo ${PATH} | awk -v RS=: -v ORS=: '$0 != "'${P}'"' | sed 's/:$//')
$ echo ${PATH}
/sbin:/bin:/usr/sbin:/usr/bin
$ # 今度はパスの順番を入れ替えて再度export
$ export PATH=/home/vagrant/tmp:/home/vagrant:${PATH}
$ env foo.sh
This function\'s location is /home/vagrant/tmp.

確かに${PATH}の前にあるパスから先に読まれている.
本当はこういうの,カーネルのソースコードとか読んでどうなってるのか見てみたいんだけど,どこから探して見ればいいのかあまり分かってないので仕方なくこうやって実験...

man envinfo coreutils 'env invocation'でドキュメント見てみたんだが,”env COMMAND${PATH}を前から見て最初に見つかったコマンドを実行します”という旨の記述が見当たらなかった.

参考