Node.jsでWindowsのパスを書くと壊れる。SyntaxErrorと、黙って化けるパターン。

Node.jsのスクリプトに、Windowsのパスをそのまま貼り付けたときの話です。

スポンサーリンク

実行すると、スクリプトの中身が動き出す前に落ちます。

SyntaxError: Invalid hexadecimal escape sequence

パスの綴りは合っていますし、そのフォルダも実在します。それでも落ちます。

問題はパスではなく、パスを文字列として書いたところにあります。そして厄介なのは、エラーにならず黙って別のパスに化ける書き方が混ざっていることです。

  1. Windowsのパスを貼ると SyntaxError になる理由
  2. エラーになる頭文字と、黙って化ける頭文字の一覧
  3. 直し方4つと、どれを選ぶか
  4. 化けているかどうかを1行で確かめる方法
  5. JSONファイルに書くときの注意点

原因は、円記号がエスケープの開始記号だからです。

JavaScriptの文字列リテラルでは、バックスラッシュ(Windowsでは円記号として表示されます)が特別な意味を持ちます。

「次の1文字は、そのままの文字ではありません」という合図です。

ところがWindowsのパス区切りも、同じ記号です。

たとえばXAMPPのPHPを呼びたくて、こう書いたとします。

console.log("C:xamppphpphp.exe");

これを実行すると、次のようになります。

console.log("C:xamppphpphp.exe");
               ^^^^

SyntaxError: Invalid hexadecimal escape sequence

指されているのは xam の部分です。

x は「16進エスケープ」の開始で、そのうしろに16進数2桁が来る決まりです。x41 と書けば A になります。

ここでは x のうしろが am なので、16進数として読めません。それで構文エラーになります。

フォルダ名が xampp だから起きたのではありません。x で始まるフォルダ名なら何でも起きます。

スポンサーリンク

u で始まるフォルダも落ちます。

u も同じ仲間で、こちらはUnicodeエスケープです。

console.log("C:userstest");
SyntaxError: Invalid Unicode escape sequence

ここまでは、まだ親切です。実行前に止まるので、間違いに気づけます。

スポンサーリンク

本当に困るのは、エラーが出ないほうです。

同じ書き方でも、フォルダ名の頭文字によってはエラーになりません。

そのかわり、中身がまったく別の文字列になります。

制御文字は目で見えないので、次のコードでは32未満の文字コードを <9> のように数字で出しています。

const show = s => s.split("").map(c =>
  c.charCodeAt(0) < 32 ? "<" + c.charCodeAt(0) + ">" : c
).join("");

console.log(show("C:tempbuild"), "/ 長さ", "C:tempbuild".length);
console.log(show("C:Usersguest"), "/ 長さ", "C:Usersguest".length);
C:<9>emp<8>uild / 長さ 11
C:Usersguest / 長さ 12

1行目の <9> はタブ、<8> はバックスペースです。

t と b が、そういう文字として解釈されました。

2行目はもっと静かです。U や g という組み合わせに決まった意味がないので、バックスラッシュだけが消えます。

区切りのないパスができあがり、エラーも警告も出ません。

頭文字ごとの結果をまとめます。

  • x, u で始まる … 構文エラーで止まります。
  • t, n, r, b, f, v で始まる … タブや改行などに化けます。エラーは出ません。
  • それ以外 … バックスラッシュが消えて、区切りがなくなります。エラーは出ません。

Windowsでよく触るフォルダ名を思い浮かべると、temp、node_modules、bin、fonts あたりが2番目に当てはまります。

スポンサーリンク

直し方は4つあります。

どれも結果は同じです。書きやすいものを選んでください。

// 1. スラッシュに書き換える
"C:/temp/build"

// 2. バックスラッシュを2つ重ねる
"C:\temp\build"

// 3. テンプレートリテラルを String.raw で囲む
String.raw`C:tempbuild`

// 4. path で組み立てる
require("path").join("C:\", "temp", "build")

実行すると、4つとも同じ文字列になります。

C:/temp/build
C:tempbuild
C:tempbuild
C:tempbuild

おすすめは1番のスラッシュです。

Windowsのファイルシステムはスラッシュ区切りも受け付けるので、fs.readFileSync("C:/temp/build/a.txt") はそのまま読めます。

そして何より、書き換えのときにエスケープを数えなくて済みます。

エクスプローラーからコピーしたパスを見た目のまま貼りたいときは、3番の String.raw が便利です。中身に一切手を入れなくてよいので、貼り付けだけで済みます。

化けているかどうかは、長さで分かります。

エラーが出ないパターンに当たると、画面に出したときも正しく見えることがあります。

疑わしいときは、文字数を数えるのが早いです。

console.log(p.length, JSON.stringify(p));

C:tempbuild は本来13文字です。11文字なら、区切りが2つとも別の文字に化けています。

ファイルを開こうとして失敗したときは、例外の path プロパティも見てください。

try {
  fs.readFileSync(p);
} catch (e) {
  console.log(e.code, JSON.stringify(e.path));
}

Nodeが実際に探しに行ったパスが出ます。JSON.stringify を通すと、タブや改行が t n として見えるようになります。

ENOENTなのにパスが合っているように見えるときは、たいていここで化けています。

JSONファイルに書くときは、逃げ道がありません。

設定ファイルにパスを書くこともあると思います。

JSONの文字列は、JavaScriptよりも規則が厳しくなっています。

JSON.parse(String.raw`{"p":"C:Users"}`);
SyntaxError: Bad escaped character in JSON at position 9

JavaScriptでは黙って通っていた U が、JSONでは弾かれます。意味のないエスケープを許さないためです。

一方で t は、JSONでも正しいエスケープです。

JSON.parse(String.raw`{"p":"C:temp"}`).p.length; // 6

C:temp は7文字のはずですが、6文字になっています。エラーは出ないまま、タブに化けました。

JSONにWindowsのパスを書くときは、スラッシュにするか、バックスラッシュを2つ重ねるかの二択です。String.raw のような逃げ道はありません。

まとめです。

  • Windowsのパスをそのまま文字列に貼ると、区切りがエスケープとして読まれます。
  • x, u で始まるフォルダ名なら構文エラーになり、実行前に気づけます。
  • t, n, r などで始まると、エラーが出ないまま別の文字に化けます。こちらのほうが厄介です。
  • スラッシュに書き換えるのが一番確実です。Windowsでもそのまま動きます。
  • 貼り付けたままにしたいときは String.raw を使います。
  • JSONではスラッシュか二重のバックスラッシュのどちらかにします。

この記事の動作は、Node.js 24 のWindows環境で確認しています。

スポンサーリンク
スポンサーリンク