CUBE SUGAR CONTAINER

技術系のこと書きます。

シェルスクリプトの中でスクリプトのあるディレクトリを取得する

シェルスクリプトの中から、実行したスクリプトのあるディレクトリを必要とする場面はちょいちょいある。 たとえば、スクリプトの中で相対パスを使って別のファイルを読み込むような処理が典型的だと思う。 その場合、スクリプトを実行したときのカレントディレクトリを起点として相対パスを処理してしまうと上手くいかない。 代わりに、スクリプトのあるディレクトリを把握した上で、そこを起点に相対パスを処理する必要がある。 今回は、それを実現する方法についてメモしておく。

使った環境は次のとおり。

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.15.7
BuildVersion:   19H15
$ bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin19)
Copyright (C) 2007 Free Software Foundation, Inc.
$ dirname --version
dirname (GNU coreutils) 8.32
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by David MacKenzie and Jim Meyering.

もくじ

下準備

今回のサンプルコードは、macOS に標準で入っている BSD dirname でも動作する。 ただし、検証に使っている環境としては GNU Coreutils の実装を利用している。

$ brew install coreutils
$ alias dirname="gdirname"

スクリプトのあるディレクトリを取得する

早速だけど、以下にサンプルコードを示す。 以下のサンプルコードでは SCRIPT_DIR というシェル変数に、スクリプトのあるディレクトリを絶対パスで代入している。

#!/usr/bin/env bash

set -CEuo pipefail

# dirname $0 でスクリプトの存在するディレクトリが得られる
# インラインコマンドで cd して pwd すれば絶対パスが得られる
SCRIPT_DIR=$(cd $(dirname $0) && pwd)
echo ${SCRIPT_DIR}

上記を適当な名前と場所に保存しよう。 ここでは example.sh という名前で /tmp ディレクトリに保存した。

まずは、試しに Bash に絶対パスでシェルスクリプトを指定して実行してみる。

$ bash /tmp/example.sh 
/tmp

ちゃんと保存されているディレクトリの絶対パスが表示された。

続いてはルートディレクトリに移動してから相対パスでシェルスクリプトを指定してみよう。

$ cd /
$ bash ./tmp/example.sh 
/tmp

これでも、ちゃんと保存されているディレクトリの絶対パスが表示された。

ついでに、実行権限をつけて、Shebang 経由でスクリプトを実行してみよう。

$ chmod +x example.sh
$ ./tmp/example.sh    
/tmp

これも、ちゃんと表示される。

動作原理について

あとはもう蛇足だけど、どうして先ほどのサンプルコードが動作するかの説明をしていく。

変数 $0 について

そもそも、シェルスクリプトでは $0 という変数にスクリプトが実行されたときのパスが入る。 たとえば、次のようなサンプルコードを用意してみよう。

#!/usr/bin/env bash

set -CEuo pipefail

# 最初の引数は実行するときに使ったパスになる
echo $0

上記を、先ほどと同じように色々な呼び方で実行してみよう。

$ bash /tmp/example.sh 
/tmp/example.sh
$ bash ./tmp/example.sh
./tmp/example.sh
$ ./tmp/example.sh    
./tmp/example.sh

呼び方によって絶対パスだったり相対パスだったりが入っている。

dirname(1) について

そして、dirname(1) を使うとパスの中でディレクトリの部分が得られる。 たとえば、シェルから直接呼んでみると、次のようになる。

$ dirname /tmp/example.sh 
/tmp
$ dirname ./tmp/example.sh 
./tmp

これで、ひとまずスクリプトのあるディレクトリが相対パスで得られた。 あとは、扱いやすさを考えて必要に応じて絶対パスにする。 絶対パスにするときはインラインで cd して pwd すれば良い。

$ (cd $(dirname ./tmp/example.sh) && pwd)
/tmp
$ (cd $(dirname /tmp/example.sh) && pwd) 
/tmp

いじょう。

補足

シンボリックリンクが使われている場合には、必要に応じて pwd(1) に -P オプションを指定して解決した方が良いかも。