rcmdnk's blog

ソース―あなたの人生の源はワクワクすることにある。

BashやZshのsourceして使う様な設定ファイルの中で そのファイル自身のパスを取得したいと言う話。

Sponsored Links

やりたいこと

例えば~/v1.0.0~/v2.0.0…みたいに何らかのプログラムとか プロジェクトをバージョン毎に管理してたりするとき、 その中にsetup.shみたいなのを置いておいて、そのファイルの中で、

export PATH=~/v1.0.0/bin:$PATH
export LD_LIBRARY_PATH=~/v1.0.0/lib:$LD_LIBRARY_PATH

みたいな設定をしたいとします。

この様に直接PATHを書いてしまうと、新しいバージョンになった時に この設定ファイル自体も書き換えが必要です。

また、違う環境に持っていった時にPATHが変わってしまう可能性があります。

なので、通常は、この様な設定ファイルの中では

export PATH=$PROJECT_PATH/bin:$PATH
export LD_LIBRARY_PATH=$PROJECT_PATH/lib:$LD_LIBRARY_PATH

みたいにして、PROJECT_PATHの様な別の環境変数を参照するようにして、 実際sourceする際に、

$ export PROJECT_PATH=~/v1.0.0
$ source ~/v1.0.0/setup.sh

みたいにする方が素直に出来ます。

ですが、これだと1回余計な設定が必要になってしまいます。

source ~/v1.0.0/setup.shの時点でそのディレクトリを見てるわけなので ファイルのなかで自動で自分のパスを取得して設定してほしいな、と。

BASH_SOURCE

以下、Bash 4.3.33でやっています。

Bashの通常のスクリプトだと、引数の0番目、$0、にファイルへのパスが入っています。

path.sh
1
2
#!/usr/bin/env bash
echo $0

これを実行すると(直接、またはbashの引数として):

$ ./path.sh
./path.sh
$ bash ./path.sh
./path.sh
$ mkdir test && cd test
$ ../path.sh
../path.sh
$ cd ../

ただ、source(または.)すると

$ source path.sh
/usr/local/bin/bash

と、現在使ってるシェルが$0に入っています。

これは

$ echo $0
/usr/local/bin/bash

となるように、現在のbashのセッションの中でそのままコマンドが実行されてる のと同じだとみなされるからです。

この様な場合は代わりにBASH_SOURCEというシェル変数があって、 ここにsourceで呼び出されたファイルのパスが入っています。

path2.sh
1
2
3
#!/usr/bin/env bash
echo $0
echo $BASH_SOURCE

とすると、

$ ./path2.sh
./path2.sh
./path2.sh
$ source ./path2.sh
/usr/local/bin/bash
./path2.sh
$ cd test
$ ../path2.sh
../path2.sh
../path2.sh
$ source ../path2.sh
/usr/local/bin/bash
../path2.sh

と言った感じにBASH_SOURCEの方ではsourceした場合でもファイルのパスを取ってこれます。

これを使えば後は、

~/v1.0.0/setup.sh
1
2
3
PROJECT_PATH="$(cd "$(dirname "$BASH_SOURCE")" && pwd)"
export PATH=$PROJECT_PATH/bin:$PATH
export LD_LIBRARY_PATH=$PROJECT_PATH/lib:$LD_LIBRARY_PATH

とでもしておけばOK。

"$(cd "$(dirname "$BASH_SOURCE")" && pwd)"

の部分はdirnameでファイルのあるディレクトリを取得、 その後一旦そのディレクトリに移動してpwdをすることで ファイルのあるディレクトリへの絶対パスを取得しています。

これでどこからsource ~/v1.0.0/setup.shをしても PATHの値が

~/v1.0.0/bin:$PATH

になります。

また、同じファイルを~/v2.0.0/setup.shにおいておけば、 source ~/v2.0.0/setup.shをした場合、

~/v2.0.0/bin:$PATH

が設定されます。

Zshでは

使ったZshは5.0.5。

Zshではsourceをした場合は ${(%):-%N}${BASH_SOURCE}と同じ役割をします1

path3.sh
1
2
3
#!/usr/bin/env zsh
echo $0
echo ${(%):-%N}

この様なスクリプトを作ってみると

$ ./path3.sh
./path3.sh
./path3.sh
$ source ./path3.sh
./path3.sh
./path3.sh

となり、この場合$0でもsourceで取ってこれてる様に見えます。

ただし、これはオプションによっては使えない場合があって、

setopt nofunction_argzero

または

unsetopt function_argzero

の設定をしていると

$ setopt nofunction_argzero
$ ./path3.sh
./path3.sh
./path3.sh
$ source ./path3.sh
zsh
./path3.sh

と、sourceした時にはzshという値が入ってしまいます。 ${(%):-%N}の方はファイルへのパスのまま。

同じ様なことが.zshrcで設定した時にもオプションに関係なく起こります。

なので$0ではなく${(%):-%N}を使います。

従って、Zsh用のセットアップファイルであるならば

~/v1.0.0/setup.sh
1
2
3
PROJECT_PATH="$(cd "$(dirname "${(%):-%N}")" && pwd)"
export PATH=$PROJECT_PATH/bin:$PATH
export LD_LIBRARY_PATH=$PROJECT_PATH/lib:$LD_LIBRARY_PATH

の様にしておけばOK。

Bash/Zsh両用

もし、Bash/Zsh両方で使いたければ

~/v1.0.0/setup.sh
1
2
3
4
5
6
7
if [ "$ZSH_VERSION" != "" ];then
  PROJECT_PATH="$(cd "$(dirname "${(%):-%N}")" && pwd)"
else
  PROJECT_PATH="$(cd "$(dirname "$BASH_SOURCE")" && pwd)"
fi
export PATH=$PROJECT_PATH/bin:$PATH
export LD_LIBRARY_PATH=$PROJECT_PATH/lib:$LD_LIBRARY_PATH

のようにわけるか、まとめて

~/v1.0.0/setup.sh
1
2
3
PROJECT_PATH="$(cd "$(dirname "${BASH_SOURCE:-${(%):-%N}}")" && pwd)"
export PATH=$PROJECT_PATH/bin:$PATH
export LD_LIBRARY_PATH=$PROJECT_PATH/lib:$LD_LIBRARY_PATH

の様に、BASH_SOURCEを探して、無ければZsh用の変数を検索、 みたいにすることも出来ます。

Sponsored Links
  1. man zshmiscを見ると%N

    The name of the script, sourced file, or shell function that zsh is currently executing, whichever was started most recently. If there is none, this is equivalent to the parameter $0. An inte- ger may follow the `%’ to specify a number of trailing path com- ponents to show; zero means the full path. A negative integer specifies leading components.

    とのこと。

Sponsored Links

« SVNで管理下のファイルを実行ファイルにする方法 LaTeXで書いた英文をWordでスペル、文法チェックする »