Menu

Node.js pathモジュール:path.joinとpath.resolveの違い

pathモジュールは、OSに合ったやり方でファイルパスを組み立てたり分解したりします。path.joinはセグメントをつなぎ、path.resolveは現在のフォルダを起点に絶対パスへ変換します。

このページのコードはエディタで実行できます。編集してすぐに結果を確認できます。

path.join() はパスのセグメントを正しい区切り文字でつなぎ、.. や重複したスラッシュを整理しますが、最初が相対セグメントなら結果も相対パスのままです。path.resolve() は同じ処理を右から左へ行い、必ず絶対パスを返します。現在の作業ディレクトリから始まり、途中に絶対パスのセグメントがあればそこからやり直します。パスを組み立てるなら join、絶対パスが必要なら resolve を使います。

users/ada/notes.txt、次に users/grace/todo.txt(.. で ada が消えました)、そして /srv/app/public/index.html と src/lib/utils.js が表示されました。余分なスラッシュと空のセグメントは消えています。join は /public/ を新しい起点として扱わず、単なる次のセグメントとして扱います。

path.joinとpath.resolveの違い

違いが出るのは、セグメントが / で始まるときと、最初のセグメントが相対パスのときです。

途中に /public があると、join は /srv/app/public/img を作り、resolve はそれより前をすべて捨てて /public/img を作りました。.. の扱いはどちらも同じです。最後の2行を見ると、join は public/index.html を相対パスのまま残し、resolve は現在のフォルダを前に付けて絶対パスにしたことがわかります。

path.joinpath.resolve
結果入力と同じく相対パスか絶対パス常に絶対パス
/ で始まるセグメントただのセグメントの1つそこからパスをやり直す
起点なし現在の作業ディレクトリ
引数なし'.'現在の作業ディレクトリ
主な使い道部品からパスを組み立てるユーザー入力や設定を絶対パスにする

basename、dirname、extname、parse

これらはパスを分解します。見ているのは文字列だけなので、ファイルが存在する必要はありません。

cat.final.jpg、cat.final、/home/ada/photos、.jpg が表示されました。extname が取るのは最後のドットより後ろの部分だけです。path.parse は全部を一度に返し(root、dir、base、ext、name)、path.format はそのオブジェクトを文字列に戻します。

__dirnameと__filename

CommonJSのファイルでは、__dirname はそのファイルがあるフォルダの絶対パスで、__filename はファイル自体のパスです。'data.json' のような相対パスは、ユーザーが node を実行した場所からの相対になるので、誰かが別のフォルダからスクリプトを起動した途端に壊れます。代わりに __dirname からパスを組み立てましょう。

config.json dark と index.js が表示されました。フルパスはマシンごとに違います。だからこそ手で書かずに path.join(__dirname, ...) で組み立てるのです。

ESモジュールの__dirname:import.meta.dirname

ESモジュールには __dirname がなく、使うと ReferenceError: __dirname is not defined in ES module scope が投げられます。Node.js 20.11以降は import.meta.dirname と import.meta.filename がその代わりです。

index.js と data/users.json が表示されました。20.11より前のNode.jsでは、モジュールのURLからフォルダを求めます。node:url の fileURLToPath を使って path.dirname(fileURLToPath(import.meta.url)) と書きます。

文字列でパスをつながない理由

'data' + '/' + file はmacOSやLinuxでは動きますが、区切り文字が \ のWindowsでは失敗しますし、..、二重のスラッシュ、スラッシュの抜けにも対処しません。path は動いているシステムのルールを使います。Windowsでの結果は、どのマシンからでも path.win32 で確認できます。

const path = require('path');

path.posix.join('users', 'ada', 'notes.txt'); // 'users/ada/notes.txt'
path.win32.join('users', 'ada', 'notes.txt'); // 'users\\ada\\notes.txt'
path.sep;                                     // '/' on macOS and Linux, '\\' on Windows

path.join の結果をfs.readFileに渡せば、同じコードがどのシステムでもファイルを読めます。

よくある間違い

extname が二重の拡張子を理解すると思う。 path.extname('archive.tar.gz') は '.gz' で、.env のようなドットファイルには拡張子がまったくありません。path.extname('.env') は '' です。末尾全体が大事なときは、名前の終わりを自分で確認してください。

join がユーザーをフォルダの中にとどめてくれると信じる。 path.join('/srv/app', '../../etc/passwd') は '/etc/passwd' です。リクエストで指定されたファイルを読む前に、パスを解決して、それがまだ自分のフォルダで始まっているかを確認しましょう。

よくある質問

__dirnameとprocess.cwd()の違いは何ですか?

__dirname は今のソースファイルがあるフォルダで、変わることはありません。process.cwd() はユーザーが node を実行したフォルダです。両者が一致するのは、プログラムを自分のフォルダから起動したときだけです。

__dirnameと組み合わせるならpath.joinとpath.resolveのどちらですか?

path.join(__dirname, 'data.json') ならどちらでも動きます。__dirname はもともと絶対パスだからです。ユーザーが入力したパスのように、セグメント自体が絶対パスかもしれず、そちらを優先させたいときは resolve を使います。

URLにpathモジュールは使えますか?

使わないでください。URLは常に / を使い、.. やクエリ文字列に独自のルールがあります。new URL('../img/logo.png', 'https://example.com/docs/page') のように URL クラスを使いましょう。

Coddyのプログラミング言語のイラスト

Coddyでコードを学ぼう

始める