TL;DR
1 cd $(dirname $0)
1 cd "$(dirname "$0")"
以上!!!!!
シェルスクリプトがあるディレクトリのパスを取る
想定状況
例えば以下のようなディレクトリ構成を想像してください。
1 tree
2 .
3 └── a
4 ├── dirname.sh
5 └── test.txt
何の変哲もない a
というディレクトリの配下に dirname.sh
と test.txt
があります。
このとき、実行パスに関わらず dirname.sh から test.txt
を読みたい、となるときがあると思います。
test.txt の中身は以下の通り
1 This is a.
どこから実行しても test.txt の内容 This is a.
が表示したい!この状況のとき、どうしますか?
よくある対処法
以下のようにすることで対処できるよ、と書いてある記事をよく見かけます。
1 cd $(dirname $0)
dirname
は与えられた引数の親ディレクトリを取得するコマンドです。$0
はそのスクリプトファイルそのものの絶対パスが入っています。
よってこれらが正しく動作するとスクリプトファイルの親ディレクトリが dirname $0
で取れるので、そこに対して cd
してやることでどこから実行してもスクリプトファイルがあるディレクトリに cd できるよという代物です。
ディレクトリを移動してしまえば、あとは適当に cat test.txt
とかすれば OK ですね!
ほんとにぃ?
ダメな例
実は上記の方法はうまく行かない例があります。
そうですね。絶対パスのどこかにスペースが入っている場合です。
簡易的に実験するために以下のようなディレクトリ構成にします。
1 tree
2 .
3 ├── a
4 │ ├── dirname.sh
5 │ └── test.txt
6 └── a 2
7 ├── dirname.sh
8 └── test.txt
わかりやすくするために a 2/test.txt
の内容を以下のように変えてみましょう。
1 This is a 2.
dirname.sh の内容を以下のようにして実行してみてください。
1 #!/bin/sh
2
3 cd $(dirname $0)
4
5 cat test.txt
1 "./a 2/dirname.sh"
2 cat: 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箇所ともダブルクォーテーションで囲みましょう。
1 #!/bin/sh
2
3 cd "$(dirname "$0")"
4
5 cat test.txt
$0
だけをダブルクォーテーションで囲ってしまうと、dirname
した結果のパスにスペースが入ってしまうので cd
でおかしくなります。
もちろん外側だけダブルクォーテーションで囲ってしまうと dirname $0
で2つパスが出てきておかしくなります。
このように丁寧にダブルクォーテーションで囲ってあげるのが大切です。
ディレクトリにスペースを突っ込むなと言われるとそうなのですが、例えばダウンロードディレクトリとかで同じファイル名の zip をダウンロードして解凍すると なんとかかんとか 2.zip
みたいなディレクトリに展開されたりしがちなので、書き捨てのコードとかだと意外と起きるケースだと思っています。
意外と世の中を見ていると cd $(dirname $0)
というコードを見たり、`cd “$(dirname $0)” という惜しいコードを見かけます。
皆さんは気をつけてくださいね!!!!!(1敗)