Quantcast
Channel: Bashタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 2912

シェルスクリプトで相対パスを絶対パスに変換する正しい方法

$
0
0

問題点

某所でファイルの相対パスから絶対パスに変換する方法として以下のようなものが紹介されていました。

path="$(cd$(dirname"$1")&&pwd)/$(basename"$1")"

これにはいくつか問題がありますが、わかるでしょうか?

答え

  1. cdの引数がダブルクォートで括られていないので空白が入るパスを正しく扱えない
  2. -で始まるパスをオプションと誤認識する
  3. 改行で終わるパスを正しく扱えない
  4. ルート上にあるファイルを指定すると /ではなく //で始まるパスとなる
  5. zsh の場合、環境変数 PATHが上書きされる
  6. 存在しないディレクトリが含まれていた場合、エラーが出力されるもののエラーにならずに間違ったパスに変換される

1, 2 に関しては簡単なミスレベルなので以下のように書き換えるだけで OK です。

path="$(cd--"$(dirname--"$1")"&&pwd)/$(basename--"$1")"

注意 古い zsh 4.0 あたり以前は cd --に対応していません。Busybox 1.17.0 あたり以前は dirname --, basename --に対応していません。

3 はコマンド置換を使うと末尾の連続する改行が取り除かれる事によるものです。例えば以下のような挙動です。

$ printf'foo\n\n\n'
foo


# ↑空行が2行出力される$ echo"$(printf'foo\n\n\n')"
foo
# 空行は表示されない

改行で終わるパスはレアケースなのでそんなパスを使うなということで良いと思いますが、コマンド置換を使って対応するならこのようになります。

$ var="$(printf'foo\n\n\n';echo _)"$ var="${var%_}"$ printf'%s'"$var"
foo


# ↑空行が2行出力される

4 は pwdの出力が /の場合を考えると //になるのは明らかです。//で始まるパスでもファイルにアクセスすることはできますが、パスを文字列として比較している場合などで問題が出る可能性があります。

5 は知らないとハマってしまいますが zsh では 変数 pathは 環境変数 PATHと紐付いる特殊変数です。環境変数 PATHと同じ内容で :区切りのパスを配列として参照することができます。そのため path=abcと実行してしまうと 環境変数 PATHまで abcとなってしまいます。

$ path=abc
$ echo$PATH
abc

$ path=(foo bar baz)$ echo$PATH
foo:bar:baz

$ date
zsh: command not found: date# PATH が 書き換えられているためコマンドが実行できなくなる

6 が一番問題でエラーにならずに間違ったパスに変換され処理が進んでしまうため重大なバグを引き起こす可能性があります。こうなる理由は cdで発生したエラーが後続の basenameの実行で隠蔽されてしまうからです。

$ cd /tmp

$ cat test.sh # test.sh に以下の内容で作成#!/bin/shset-e# エラーで停止するようにしても効果はありませんabspath="$(cd--"$(dirname--"$1")"&&pwd)/$(basename--"$1")"echo$?echo"$abspathを削除"$ sh ./test.sh no-directory/file # 存在しないディレクトリ上のファイルを指定
./test.sh: 3: cd: can’t cd to no-directory # ← エラーメッセージは表示される
0 # ← 終了ステータスがエラーではない
/file を削除 # ← ルートディレクトリ上のファイルを削除しようとしてしまう

解決方法

これらの問題を解決した正しい方法は次のようなものです。(3. 改行で終わるパスへの対応は面倒なので省略します。)

# set -e していれば 下記の || exit $? は不要dirname="$(cd--"$(dirname--"$1")"&&pwd)"||exit$?abspath="${dirname%/}/$(basename--"$1")"

dirnamebasenameはエラーになることはないという前提にしています。ならないですよね・・・?)

別解(シェル関数による実装)

簡易な(行数が少ない)コードを求めているのであれば上記のコードで良いのですがシェルスクリプトにするのであれば行数を気にする必要はないと思うのでシェル関数にしました。上記のすべての問題への対応に加えパフォーマンスも向上させています。また相対パスから絶対パスへの変換だけでなく、その逆も実装ししたので別記事にしています。

また関連記事としてこちらもどうぞ


Viewing all articles
Browse latest Browse all 2912

Trending Articles