シェルスクリプトがある場所の絶対パスを取る。これには罠がある

2024/6/3

dirname $0 には罠がある。それを紹介します。 失敗するとうまく cd できないとかが起こります。

TL;DR

ダメな例👎
1cd $(dirname $0)
良い例👍
1cd "$(dirname "$0")"

以上!!!!!

シェルスクリプトがあるディレクトリのパスを取る

想定状況

例えば以下のようなディレクトリ構成を想像してください。

1tree
2.
3└── a
4 ├── dirname.sh
5 └── test.txt

何の変哲もない a というディレクトリの配下に dirname.shtest.txt があります。
このとき、実行パスに関わらず dirname.sh から test.txt を読みたい、となるときがあると思います。

test.txt の中身は以下の通り

a/test.txt
1This is a.

どこから実行しても test.txt の内容 This is a. が表示したい!この状況のとき、どうしますか?

よくある対処法

以下のようにすることで対処できるよ、と書いてある記事をよく見かけます。

1cd $(dirname $0)

dirname は与えられた引数の親ディレクトリを取得するコマンドです。
$0 はそのスクリプトファイルそのものの絶対パスが入っています。

よってこれらが正しく動作するとスクリプトファイルの親ディレクトリが dirname $0 で取れるので、そこに対して cd してやることでどこから実行してもスクリプトファイルがあるディレクトリに cd できるよという代物です。

ディレクトリを移動してしまえば、あとは適当に cat test.txt とかすれば OK ですね!

ほんとにぃ?

ダメな例

実は上記の方法はうまく行かない例があります。
そうですね。絶対パスのどこかにスペースが入っている場合です。

簡易的に実験するために以下のようなディレクトリ構成にします。

1tree
2.
3├── a
4│ ├── dirname.sh
5│ └── test.txt
6└── a 2
7 ├── dirname.sh
8 └── test.txt

わかりやすくするために a 2/test.txt の内容を以下のように変えてみましょう。

1This is a 2.

dirname.sh の内容を以下のようにして実行してみてください。

うまくいかない
1#!/bin/sh
2
3cd $(dirname $0)
4
5cat test.txt
1"./a 2/dirname.sh"
2cat: test.txt: No such file or directory

はい。残念ながらうまくいきません。

どうすればよいのか?

なぜうまくいかないのか?

スペースが入っていると、その時点で別の引数として処理されてしまうことが原因です。

上記における dirname $0$0 には次のような文字列が入ります。
/path/to/top/a 2/dirname.sh
よって次のように解釈されます。

dirname /path/to/top/a 2/dirname.sh

つまり dirname に対して1つの引数ではなく複数の引数を与えてしまっています。
dirname は引数を複数取ることができ、それぞれに対して親ディレクトリを返します。
したがって最終的に cd /path/to/top 2 みたいなコマンドとして評価されてしまいます。

そりゃうまくいくわけないです。

ダブルクォーテーションで区切れば OK

というわけで一連の文字列として引数を与えてやればよいので、ダブルクォーテーションで囲めば OK です。
もちろん展開される場所2箇所ともダブルクォーテーションで囲みましょう。

Good👍
1#!/bin/sh
2
3cd "$(dirname "$0")"
4
5cat test.txt

$0 だけをダブルクォーテーションで囲ってしまうと、dirname した結果のパスにスペースが入ってしまうので cd でおかしくなります。
もちろん外側だけダブルクォーテーションで囲ってしまうと dirname $0 で2つパスが出てきておかしくなります。

このように丁寧にダブルクォーテーションで囲ってあげるのが大切です。
ディレクトリにスペースを突っ込むなと言われるとそうなのですが、例えばダウンロードディレクトリとかで同じファイル名の zip をダウンロードして解凍すると なんとかかんとか 2.zip みたいなディレクトリに展開されたりしがちなので、書き捨てのコードとかだと意外と起きるケースだと思っています。

意外と世の中を見ていると cd $(dirname $0) というコードを見たり、`cd “$(dirname $0)” という惜しいコードを見かけます。
皆さんは気をつけてくださいね!!!!!(1敗)

カテゴリー
プログラミング言語
フレームワーク/ライブラリ
ハードウェア
その他技術系
雑記