やってみる

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

Bashファイルパスを取得する関数を作った(自分自身・呼出元)

 API

成果物

実行

mkdir -p /tmp/work
cd /tmp/work
git clone https://github.com/ytyaru/Shell.Path.20200118125311
cd /src
./test_path.sh
./call_path.sh

 結果は以下。

Cleared all test. 20
SelfPath    : /tmp/work/Shell.Path.20200118125311/src/path.sh
SelfName    : path.sh
SelfExt     : sh
SelfNameId  : path
CallerPath  : /tmp/work/Shell.Path.20200118125311/src/call_path.sh
CallerName  : call_path.sh
CallerExt   : sh
CallerNameId: call_path

コード

path.sh

#!/usr/bin/env bash
SelfPath() { echo "$(__Join "$(SelfParent)" "$(SelfName)")"; }
SelfParent() { echo "$(__Parent "${BASH_SOURCE:-0}")"; }
SelfName() { echo "$(__Name "${BASH_SOURCE:-0}")"; }
SelfExt() { echo "$(__Ext "${BASH_SOURCE:-0}")"; }
SelfNameId() { echo "$(__NameId "${BASH_SOURCE:-0}")"; } # WithoutExt

CallerPath() { echo "$(__Join "$(CallerParent)" "$(CallerName)")"; }
CallerParent() { echo "$(__Parent "$0")"; }
CallerName() { echo "$(__Name "$0")"; }
CallerExt() { echo "$(__Ext "$0")"; }
CallerNameId() { echo "$(__NameId "$0")"; } # WithoutExt

__Join() { args=("$@"); echo "$(IFS=/; echo "${args[*]}")"; }
__Parent() { echo "$(cd "$(dirname "$1")"; pwd)"; }
__Name() { echo "$(basename "$1")"; }
__Ext() { local n="$(__Name $1)"; echo "${n##*.}"; }
__NameId() { local n="$(__Name $1)"; echo "${n%.*}"; }

問題

ライブラリをロードする時点で、ライブラリで実装した内容の一部を生で書かねばならない。

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

 本当は以下のように相対パスで書きたいが問題あり。

. ./sub/path.sh

 相対パスだと参照できない場合がある。そのスクリプト絶対パス指定でコマンド実行したとき、そのときのカレントディレクトリが相対パスのベースになってしまう。もし実行時のカレントディレクトリが、相対パスで省略したパスでなければ参照できない!

 つまり、相対パスで参照するためには、実行時のカレントディレクトリが、相対パスで省略したパスであるべきである。

 これは現実的でない。ふつうはカレントディレクトリに関係なく、そのファイルが存在するディレクトリパスをベースとして欲しいはず。そのように動作させるためには、先述の$BASH_SOURCEを含むコードを書かねばならない。

 この問題は次のような問題に発展する。

  • モジュール化困難
    • ファイル毎に冗長なコードを書かねばならない
    • ファイル毎にパスを気にせねばならない

原因

 原因はbash言語がimport機能を持っていないこと。以下のように言語拡張してくれないかな。

import sub/path.sh
echo "$(path.SelfPath)"

対策

 自分でimportコマンドを作ればいける? それを環境変数PATHに通せば。

import sub/path.sh
echo "$(path.SelfPath)"

 だが、importは呼出元の子プロセスになってしまう。子プロセスでロードした環境変数は親プロセスで使えない……。

 では.(source)で親自身の一部としてロードしたらどうか?

. import sub/path.sh
echo "$(path.SelfPath)"

 import自体をimportしているかのようなコードで違和感がある。でもこれで動くなら妥協できるか?

 名前空間みたいに.をつけることはできるのか?

 関数のリネームは以下が参考になりそう。

対象環境

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