はじめに
Pythonの標準ライブラリの
- os.urllib.parse.urljoin
- os.path.join
- os.path.normpath
のスニペット事例紹介です。
特に、os.urllib.parse.urljoinについては、
「/a/b/c/foo.html」と「../../common/img/z.jpg」を引数に与えると、「/a/common/img/z.jpg」を直接戻してくれる
という、相対パスからのルート相対パス変換@静的HTMLの分析・パッチ/変換あるあるをダイレクトに実装してくれるので、アドホックに手軽にこのようなことをやりたい場合は非常にありがたいライブラリでした。
昔話(静的HTML内の相対パスリンクをルート相対パスに変換)
実際の紹介の前に昔話をひとつ。
よくある話ですが、Bシェルのシェルスクリプトしか使えない(perlも使えない)環境で、静的html内のリンクを相対パスからルート相対パスに変換したりということがしばしばありました。
やりたいことはこんなことです。↓
この変換ですが、シェル芸のエキスパートならまだしも、そうではない自分には結構手間な話でした。
というのも次の図のように、親ディレクトリを示す「ドット2つ(..)」のパスの変換も必要になるからです。
ひとつのアイディアとして、シェルスクリプトの中で実際に「『cd』して『pwd』」して得られた値を使ってしのいでいましたが、これはこれでいろいろ手間です。
まあ、Bシェルと言っても、bashは使えたので、バッククォート版ではなくて、「$と()」によるコマンド置換*1は利用できましたのでその分は気軽でしたが...
ディレクトリやファイル名のパス結合
前述の例ぐらいなら言語によっては標準ライブラリがあるんじゃないということで、いまさらながら公式Rをみたりやググってみたところ、「結合」という意味では次の関数が使えそうです。
os.path.join docs.python.org
os.urllib.parse.urljoin docs.python.org
なお、似た観点のライブラリとして、ある相対パスに関して、実行環境での実際の絶対パスを得るという、Path.resolve()という関数もあります。ここではリンクするに留めておきます。
Path.resolve https://docs.python.org/ja/3/library/pathlib.html#pathlib.Path.resolve
やってみる
やってみた方が早そうですので、説明無用で次のスクリプトを起動してみます。
from urllib.parse import urljoin d = [ '/a/b/c/', '/a/b/c/index.html', '/a/b/c/foo.html', '/a/b/c/bar', '/a/b/c' ] for i in ['d/e/f.jgp', '../../y/z.png']: print('-----') print(i) for j in d: print('--') j_i = j.ljust(20,' ') + i.ljust(20,' ') print('ospath:',os.path.join(j, i).ljust(40,' '), j_i) print('osnorm:',os.path.normpath(os.path.join(j, i)).ljust(40,' '), j_i) print('urllib:',urljoin(j, i).ljust(40,' '), j_i)
↓ 実行結果
----- d/e/f.jgp -- ospath: /a/b/c/d/e/f.jgp /a/b/c/ d/e/f.jgp osnorm: /a/b/c/d/e/f.jgp /a/b/c/ d/e/f.jgp urllib: /a/b/c/d/e/f.jgp /a/b/c/ d/e/f.jgp -- ospath: /a/b/c/index.html/d/e/f.jgp /a/b/c/index.html d/e/f.jgp osnorm: /a/b/c/index.html/d/e/f.jgp /a/b/c/index.html d/e/f.jgp urllib: /a/b/c/d/e/f.jgp /a/b/c/index.html d/e/f.jgp -- ospath: /a/b/c/foo.html/d/e/f.jgp /a/b/c/foo.html d/e/f.jgp osnorm: /a/b/c/foo.html/d/e/f.jgp /a/b/c/foo.html d/e/f.jgp urllib: /a/b/c/d/e/f.jgp /a/b/c/foo.html d/e/f.jgp -- ospath: /a/b/c/bar/d/e/f.jgp /a/b/c/bar d/e/f.jgp osnorm: /a/b/c/bar/d/e/f.jgp /a/b/c/bar d/e/f.jgp urllib: /a/b/c/d/e/f.jgp /a/b/c/bar d/e/f.jgp -- ospath: /a/b/c/d/e/f.jgp /a/b/c d/e/f.jgp osnorm: /a/b/c/d/e/f.jgp /a/b/c d/e/f.jgp urllib: /a/b/d/e/f.jgp /a/b/c d/e/f.jgp ----- ../../y/z.png -- ospath: /a/b/c/../../y/z.png /a/b/c/ ../../y/z.png osnorm: /a/y/z.png /a/b/c/ ../../y/z.png urllib: /a/y/z.png /a/b/c/ ../../y/z.png -- ospath: /a/b/c/index.html/../../y/z.png /a/b/c/index.html ../../y/z.png osnorm: /a/b/y/z.png /a/b/c/index.html ../../y/z.png urllib: /a/y/z.png /a/b/c/index.html ../../y/z.png -- ospath: /a/b/c/foo.html/../../y/z.png /a/b/c/foo.html ../../y/z.png osnorm: /a/b/y/z.png /a/b/c/foo.html ../../y/z.png urllib: /a/y/z.png /a/b/c/foo.html ../../y/z.png -- ospath: /a/b/c/bar/../../y/z.png /a/b/c/bar ../../y/z.png osnorm: /a/b/y/z.png /a/b/c/bar ../../y/z.png urllib: /a/y/z.png /a/b/c/bar ../../y/z.png -- ospath: /a/b/c/../../y/z.png /a/b/c ../../y/z.png osnorm: /a/y/z.png /a/b/c ../../y/z.png urllib: /y/z.png /a/b/c ../../y/z.png
まとめ
上記の結果をみやすいように次の表にしました。
各関数の位置付け・生い立ちからすると当たり前ですが、html内の相対パスを変換するという用途については、やはりurljoinが都合が良さそうです。
ただし、os.path.joinとnormpathからすると、スラッシュ有無を起点にディレクトリかどうかを判断しているようですので、2,3,7,8あたりの結果にケチをつかられるのはある意味いいがかりかもしれません。
また、「/a/b/c」の例の動作を例にとって、os.path.joinの方がurljoinよりも用途にあっているという場合もしばしばあるかもしれません。
以上、「昔話」を成仏させるために筆をとってみました。
おまけ
ターゲットとなるファイルのルート相対パスと起点となるディレクトリ・ファイルが分かっていて、ターゲットとなるファイルの相対パスが欲しい場合もあります。
次の図のような今回の本題と反対の演算です。
こちらは、次のように、os.path.relpathを使うと、あっさり対応できそうです。
In [1]: import os In [2]: os.path.relpath('/a/y/z.png',start='/a/b/c/') Out[2]: '../../y/z.png' ※ startに渡すディレクトリの形式には注意!
os.path.relpath
その他参考リンク
記事本文ではos.pathを例に挙げましたが、pathlibにシフトしていくべきなのかもしれません。
※ ページの最後に、os.pathと pathlibの同等のものの対応表があります。