17
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-04-04

Linux や macOS などの *NIX 系 OS で、bash,zsh,ash スクリプト実行時にカレント・ディレクトリをパレント・ディレクトリ(親ディレクトリ)に移動したい。

でもね。ディレクトリ名にスペースがあるのよ。あと、スクリプトは相対パスで実行したい。確実な方法はないか。

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

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

bash スクリプト 親ディレクトリ 移動」で Qiita 記事に絞ってググってもタイトルから一発でわかる Qiita 記事が出てこなかったので自分のググラビリティのため。

TL; DR (今北産業)

  1. ダブルクォーテーションで括る。

  2. 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"
    
  3. 上記のポイント

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

🐒 注意点
シンボリック・リンクの場合は、スクリプト本体でなくシンボリック・リンク先のパレント・ディレクトリが返されてしまいます。

通常はシンボリック・リンク先で問題ないのですが、スクリプト本体に対して取得したい場合は -L オプション(if [ -L <ファイルのパス> ]; then ...<シンボリックの場合の処理>...; fi)でシンボリック・リンクかの切り分けをしてから、readlink コマンド(readlink -f)で取得した本体スクリプトのパスの値を使うなどの工夫が必要です。

TS; DR(概要と詳細)

基本

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

単純な取得方法と問題点

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

sample.sh
#!/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))してから親ディレクトリを抽出します。

sample.sh
#!/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)

参考文献

17
19
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?