やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

Bashで自分自身が存在するディレクトリパスを取得する(☓$0 ○$BASH_SOURCE)

 相対パスで別スクリプトを参照するときに使う。

結論

"$(cd "$(dirname "$(realpath "${BASH_SOURCE:-0}")")"; pwd)"

 自身がシンボリックリンクのとき元パスをたどらず、シンボリックリンクファイル自体のパスにしたいなら以下。

"$(cd "$(dirname "${BASH_SOURCE:-$0}")"; pwd)"

類似

使いどころ

 自分自身をルートとした相対パスにより、別のスクリプトファイルを参照する。

  • /tmp/
    • repo/
      • main.sh
      • sub.sh

main.sh

. "$(cd "$(dirname "${BASH_SOURCE:-$0}")"; pwd)/sub.sh"

 main.shからsub.shを参照する。相対パスのルートは/tmp/repoである。

 カレントディレクトリを/home/piなど全く別のパスにしても相対パスのルートは変わらず/tmp/repoである。

cd /home/pi
/tmp/repo/main.sh

 もし$0だと相対パスのルートは/home/piになってしまう。よって冒頭にある通り$BASH_SOURCEが必要。

回避した罠

$BASH_SOURCEはBash専用である  shやzshなど他のシェルには存在しない。それらでも自分自身のフルパスが欲しいときは$0を使うしかない。
$0は呼出元パスになってしまう  検証コードを書く。a.shファイルで期待するのはa.shファイルのフルパスである。

cd /tmp/work

a.sh

echo "\$0: $0"
echo "\$BASH_SOURCE: $BASH_SOURCE"

b.sh

. a.sh
chmod 755 a.sh
chmod 755 b.sh

 実行する。

./b.sh
$0: ./b.sh
$BASH_SOURCE: /tmp/work/a.sh

 期待値a.shのフルパスを取得できたのは$BASH_SOURCEだけ。$0は呼出元のb.shパスとなってしまった。しかも相対パスであり絶対パスでない。

 別のディレクトリから実行してみると$0絶対パスになった。

cd ~
/tmp/work/b.sh
$0: /tmp/work/b.sh
$BASH_SOURCE: /tmp/work/a.sh

 $0には以下2点の問題があることが判明した。

  • $0は呼出元ファイルパスである(自分自身でなく)
  • $0相対パスになることがある(絶対パスでなく)

$0相対パスになってしまうことがある  カレントディレクトリが自分自身の親であるとき$0相対パスになってしまう。

 どんなときでも$0絶対パスが欲しいときは以下のようにする。

$(cd $(dirname $0); pwd)

クォートすべきである  さもなくばエラーになりうる。エラーになるのはファイルパスにスペースが入っているのにクォートされていないときだ。

 スペースはbashのメタ文字である。もしスペースがあるのにクォートされていなければ、別コマンド・別引数として扱われてエラーになってしまう。それを防ぐためクォートする。

"$(cd "$(dirname "$0")"; pwd)"

 クォートはダブルクォートのみ有効。シングルクォートだと展開できずリテラル値になってしまうため。

シンボリックリンクが解決できない * https://stackoverflow.com/questions/24112727/relative-paths-based-on-file-location-instead-of-current-working-directory

 pwd -Pのように-Pオプションを付与することでシンボリックリンクを物理パスへと解決できる。

対象環境

$ uname -a
Linux raspberrypi 4.19.75-v7l+ #1270 SMP Tue Sep 24 18:51:41 BST 2019 armv7l GNU/Linux