シェルで実行スクリプトの親ディレクトリへ確実に移動する（bash/zsh/ash パレント ・ディレクトリの取得とスペース入りのパスの場合）

> Linux や macOS などの *NIX 系 OS で、`bash`,`zsh`,`ash` スクリプト実行時にカレント・ディレクトリをパレント・ディレクトリ（親ディレクトリ）に移動したい。
>
> でもね。**ディレクトリ名にスペースがある**のよ。あと、スクリプトは相対パスで実行したい。確実な方法はないか。

ググるとよく出てくる `dirname $(pwd)` を使った `cd $(dirname $(dirname $0))` ですが、実はスクリプトを呼び出した場所、ディレクトリ名や方法によっては**正常に動作しません**。

ディレクトリ名にスペースが含まれていたり、呼び出し元のディレクトリや呼び出し元が相対パスだったりする場合です。

[「`bash` `スクリプト` `親ディレクトリ` `移動`」で Qiita 記事に絞ってググって](https://www.google.com/search?q=site:qiita.com+bash+スクリプト+親ディレクトリ+移動&tbs=qdr:y)もタイトルから一発でわかる Qiita 記事が出てこなかったので自分のググラビリティのため。

## TL; DR <sub><sup><sub><sup>（今北産業）</sup></sub></sup></sub>

1. ダブルクォーテーションで括る。
2. [POSIX](https://ja.wikipedia.org/wiki/POSIX) はもちろんのこと bash や zsh にも対応させる。

    ```bash:スクリプトのパレント（親）ディレクトリの絶対パス取得（スペース入り対応）
    PATH_DIR_PARENT="$(dirname "$(cd "$(dirname "${BASH_SOURCE:-$0}")" && pwd)")"
    echo "$PATH_DIR_PARENT"
    ```
    ```bash:スクリプトのカレント（現）ディレクトリの絶対パス取得（スペース入り対応）
    PATH_DIR_SCRIPT="$(cd "$(dirname "${BASH_SOURCE:-$0}")" && pwd)"
    echo "$PATH_DIR_SCRIPT"
    ```
    ```bash:スクリプト名の取得（スペース入り対応）
    NAME_FILE_SCRIPT="$(basename "${BASH_SOURCE:-$0}")"
    echo "$NAME_FILE_SCRIPT"
    ```
    ```bash:スクリプトの絶対パス取得（スペース入り対応）
    PATH_FILE_SCRIPT="$(cd "$(dirname "${BASH_SOURCE:-$0}")" && pwd)/$(basename "${BASH_SOURCE:-$0}")"
    echo "$PATH_FILE_SCRIPT"
    ```

3. 上記のポイント
> 1. `source` によるインポート＆スクリプトの関数内でも取得できるように [bash のシェル変数](https://linuxjm.osdn.jp/html/GNU_bash/man1/bash.1.html#lbAW)から実行スクリプトのパスとして参照する。(`BASH_SOURCE[0]`）
> 2. [ZSH](https://ja.wikipedia.org/wiki/Z_Shell) でも実行できるように `${BASH_SOURCE:-$0}` で取得する。（`Z Shell` は macOS Catalina 以降のデフォルト・シェル）
> 3. 取得した実行スクリプトのパスを一旦絶対パスに変換する。
> 4. `dirname` コマンドで親ディレクトリを取得する。
> 5. `cd` する。その際にダブルクォーテーション**（`"`）で囲う。



- [オンラインで動作確認](https://paiza.io/projects/tkOFSb10xerCYAK8JCq5sQ) @ paiza.IO

<!--
- [オンラインで動作確認（簡易）](https://paiza.io/projects/nt3Yxa-18-5HfIaD4Jvp7w) @ paiza.IO
- [オンラインで動作確認（詳しく）](https://paiza.io/projects/dyeXSjy3pNf66QB21AwBEw) @ paiza.IO
-->

> <sup>🐒 <font color=red>注意点</font></sup>
> <sup>**シンボリック・リンクの場合**は、スクリプト本体でなくシンボリック・リンク先のパレント・ディレクトリが返されてしまいます。</sup>
>
> <sup>通常はシンボリック・リンク先で問題ないのですが、スクリプト本体に対して取得したい場合は `-L` オプション（`if [ -L <ファイルのパス> ]; then ...<シンボリックの場合の処理>...; fi`）でシンボリック・リンクかの切り分けをしてから、[readlink](https://www.google.com/search?q=linux+readlink) コマンド（`readlink -f`）で取得した本体スクリプトのパスの値を使うなどの工夫が必要です。</sup>

## TS; DR（概要と詳細）

### 基本

- スクリプト本体ファイルのパスは `$0` 変数に格納されている
    - 別プロセス（`$(xxx)`）内で使う場合は `BASH_SOURCE:-$0` が確実
- ディレクトリのパスは `dirname` コマンドを使うと取得できる
- UNIX/Linux はディレクトリもファイルと同じ扱い
    - この挙動により **`dirname` コマンドは、厳密には最下層のファイル名を削除したパスを返す関数**である。

### 単純な取得方法と問題点

上記より、ファイルのパスに対して**`dirname` を２回使うと１階層上のディレクトリが取得できる**のですが、どのように実行するかによって問題が発生します。

```bash:sample.sh
#!/usr/bin/env bash

echo $0               # スクリプト本体のパス
dirname $0            # カレントディレクトリ
dirname $(dirname $0) # パレントディレクトリ
```
```shell-session:ルートから絶対パスで実行してみる
$ cd /
$ /path/to/sample.sh
/path/to/sample.sh
/path/to
/path
```

上記のように絶対パスで呼び出す分にはいいのですが、問題は**相対パスでスクリプトを呼び出すと `$0` 変数で取得できるパスも相対パスになる**ことです。

```shell-session:相対パスで実行してみる
$ # ルートから絶対パスで実行（正しい動き）
$ cd /
$ /path/to/sample.sh
/path/to/sample.sh
/path/to
/path

$ # スクリプトの１階層上から相対パスで実行
$ cd /path
$ ./to/sample.sh
./to/sample.sh
./to
.

$ # スクリプトと同じ階層から相対パスで実行
$ cd /path/to
$ ./sample.sh
./sample.sh
.
.
```

注目すべき問題は、上記の最後の例のように**スクリプトと同じ階層で実行するとカレント・ディレクトリも親ディレクトリも同じ「`.`」になる**ことです。

そのため、スクリプト内で単純に **`cd $(dirname $(dirname $0))` を利用すると相対パスによる呼び出しでトラブルが起きる**可能性が高くなります。また、外部スクリプトから呼び出された場合なども意図しない動作を起こします。

解決方としては、一旦スクリプトのパスを絶対パスに変換（`$(cd $(dirname $0); pwd)`）してから親ディレクトリを抽出します。

```bash:sample.sh
#!/usr/bin/env bash

echo $0
dirname $0
echo $(cd $(dirname $0); pwd)
dirname $(cd $(dirname $0); pwd)
```
```shell-session
$ 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)

## 参考文献

- [シェル変数](https://linuxjm.osdn.jp/html/GNU_bash/man1/bash.1.html#lbAW) | Man page of BASH @ JM Project
- [Reliable way for a Bash script to get the full path to itself](https://stackoverflow.com/a/9107028/12102603) @ StackOverflow
- [[bash] 実行スクリプトの絶対パスの取得](https://qiita.com/koara-local/items/2d67c0964188bba39e29) @ Qiita
- [bashで親ディレクトリの絶対パスを取得する](https://qiita.com/JeJeNeNo/items/55fa1ce180fa51064237) @ Qiita
- [$0と$BASH_SOURCEの違い](https://qiita.com/tkygtr6/items/fee79f710c55e3649ec0) @ Qiita
- [bash/zshでsourceされたスクリプト内で、ファイル自身の絶対パスをとるシンプルな記法](https://qiita.com/yudoufu/items/48cb6fb71e5b498b2532) @ Qiita