Linux や macOS などの *NIX 系 OS で、
bash
,zsh
,ash
スクリプト実行時にカレント・ディレクトリをパレント・ディレクトリ(親ディレクトリ)に移動したい。でもね。ディレクトリ名にスペースがあるのよ。あと、スクリプトは相対パスで実行したい。確実な方法はないか。
ググるとよく出てくる dirname $(pwd)
を使った cd $(dirname $(dirname $0))
ですが、実はスクリプトを呼び出した場所、ディレクトリ名や方法によっては正常に動作しません。
ディレクトリ名にスペースが含まれていたり、呼び出し元のディレクトリや呼び出し元が相対パスだったりする場合です。
「bash
スクリプト
親ディレクトリ
移動
」で Qiita 記事に絞ってググってもタイトルから一発でわかる Qiita 記事が出てこなかったので自分のググラビリティのため。
TL; DR (今北産業)
-
ダブルクォーテーションで括る。
-
POSIX はもちろんのこと bash や zsh にも対応させる。
スクリプトのパレント(親)ディレクトリの絶対パス取得(スペース入り対応)PATH_DIR_PARENT="$(dirname "$(cd "$(dirname "${BASH_SOURCE:-$0}")" && pwd)")" echo "$PATH_DIR_PARENT"
スクリプトのカレント(現)ディレクトリの絶対パス取得(スペース入り対応)PATH_DIR_SCRIPT="$(cd "$(dirname "${BASH_SOURCE:-$0}")" && pwd)" echo "$PATH_DIR_SCRIPT"
スクリプト名の取得(スペース入り対応)NAME_FILE_SCRIPT="$(basename "${BASH_SOURCE:-$0}")" echo "$NAME_FILE_SCRIPT"
スクリプトの絶対パス取得(スペース入り対応)PATH_FILE_SCRIPT="$(cd "$(dirname "${BASH_SOURCE:-$0}")" && pwd)/$(basename "${BASH_SOURCE:-$0}")" echo "$PATH_FILE_SCRIPT"
-
上記のポイント
source
によるインポート&スクリプトの関数内でも取得できるように bash のシェル変数から実行スクリプトのパスとして参照する。(BASH_SOURCE[0]
)- ZSH でも実行できるように
${BASH_SOURCE:-$0}
で取得する。(Z Shell
は macOS Catalina 以降のデフォルト・シェル)- 取得した実行スクリプトのパスを一旦絶対パスに変換する。
dirname
コマンドで親ディレクトリを取得する。cd
する。その際にダブルクォーテーション**("
)で囲う。
- オンラインで動作確認 @ paiza.IO
🐒 注意点
シンボリック・リンクの場合は、スクリプト本体でなくシンボリック・リンク先のパレント・ディレクトリが返されてしまいます。通常はシンボリック・リンク先で問題ないのですが、スクリプト本体に対して取得したい場合は
-L
オプション(if [ -L <ファイルのパス> ]; then ...<シンボリックの場合の処理>...; fi
)でシンボリック・リンクかの切り分けをしてから、readlink コマンド(readlink -f
)で取得した本体スクリプトのパスの値を使うなどの工夫が必要です。
TS; DR(概要と詳細)
基本
- スクリプト本体ファイルのパスは
$0
変数に格納されている- 別プロセス(
$(xxx)
)内で使う場合はBASH_SOURCE:-$0
が確実
- 別プロセス(
- ディレクトリのパスは
dirname
コマンドを使うと取得できる - UNIX/Linux はディレクトリもファイルと同じ扱い
- この挙動により
dirname
コマンドは、厳密には最下層のファイル名を削除したパスを返す関数である。
- この挙動により
単純な取得方法と問題点
上記より、ファイルのパスに対して**dirname
を2回使うと1階層上のディレクトリが取得できる**のですが、どのように実行するかによって問題が発生します。
#!/usr/bin/env bash
echo $0 # スクリプト本体のパス
dirname $0 # カレントディレクトリ
dirname $(dirname $0) # パレントディレクトリ
$ cd /
$ /path/to/sample.sh
/path/to/sample.sh
/path/to
/path
上記のように絶対パスで呼び出す分にはいいのですが、問題は相対パスでスクリプトを呼び出すと $0
変数で取得できるパスも相対パスになることです。
$ # ルートから絶対パスで実行(正しい動き)
$ cd /
$ /path/to/sample.sh
/path/to/sample.sh
/path/to
/path
$ # スクリプトの1階層上から相対パスで実行
$ cd /path
$ ./to/sample.sh
./to/sample.sh
./to
.
$ # スクリプトと同じ階層から相対パスで実行
$ cd /path/to
$ ./sample.sh
./sample.sh
.
.
注目すべき問題は、上記の最後の例のようにスクリプトと同じ階層で実行するとカレント・ディレクトリも親ディレクトリも同じ「.
」になることです。
そのため、スクリプト内で単純に cd $(dirname $(dirname $0))
を利用すると相対パスによる呼び出しでトラブルが起きる可能性が高くなります。また、外部スクリプトから呼び出された場合なども意図しない動作を起こします。
解決方としては、一旦スクリプトのパスを絶対パスに変換($(cd $(dirname $0); pwd)
)してから親ディレクトリを抽出します。
#!/usr/bin/env bash
echo $0
dirname $0
echo $(cd $(dirname $0); pwd)
dirname $(cd $(dirname $0); pwd)
$ cd /
$ /path/to/sample.sh
/path/to/sample.sh
/path/to
/path/to
/path
$ cd /path
$ ./to/sample.sh
./to/sample.sh
./to
/path/to
/path
$ cd /path/to
$ ./sample.sh
./sample.sh
.
/path/to
/path
動作確認済み環境
- macOS Mojave(OSX 10.14.4)
- GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18)
参考文献
- シェル変数 | Man page of BASH @ JM Project
- Reliable way for a Bash script to get the full path to itself @ StackOverflow
- [bash] 実行スクリプトの絶対パスの取得 @ Qiita
- bashで親ディレクトリの絶対パスを取得する @ Qiita
- $0と$BASH_SOURCEの違い @ Qiita
- bash/zshでsourceされたスクリプト内で、ファイル自身の絶対パスをとるシンプルな記法 @ Qiita