关于nodejs使用相对路径读取文件的问题
errol发表于2024-05-19 08:42:48 | 分类为 编程 | 标签为nodejs相对路径绝对路径读取文件

在nodejs中,由于通常使用相对路径加载自定义模块文件的缘故,自然而然地,在读取磁盘文件时,首先想到的,可能也是使用相对路径的方式。

当然,并不是说相对路径不可取,只是在某些情况下,这种方式会存在问题。

一、问题定位

如以下一段示例代码:

// src/app.js

const fs = require("fs");
const { print } = require("./util/common");
const data = require("../data.json");

print("require: ");
print("---");
print(data);

print("\n");

fs.readFile("../data.json", { encoding: 'utf-8' }, (error, data) => {
    if (error) {
        throw error;
    }

    print("fs.readFile:");
    print("---")
    print(data);
});

该段代码首先从src/util/common.js中加载print()方法,接着分别使用require()和fs.readFile()加载src/data.json文件,并将内容输出到控制台。

执行文件后,得到如下结果:

image

图1 app.js正常运行

可以看到,现在这段代码是可以正常运行,无论是require(),还是fs.readFile(),都成功读取了文件内容,但是存在一个隐患

比如,当切换到其他目录执行时,会发现代码抛出“找不到文件”的异常。

image

图2 app.js [Error: ENOENT: no such file or directory, open '../data.json']

需要注意的是,错误是从fs.readFile()抛出的,require()可以正常运行。

二、问题分析

为什么会出现这种差异?

究其原因,其实是require和fs加载文件的机制不同。

在nodejs文档中,有这样一段关于文件模块fs的描述

String paths are interpreted as UTF-8 character sequences identifying the absolute or relative filename. Relative paths will be resolved relative to the current working directory as determined by calling process.cwd().

主要的是后面那句,说的是:相对路径将根据调用process.cwd()所确定的当前工作目录进行解析。

换句话说,使用文件模块fs通过相对路径读取文件时,相对的是process.cwd()的返回结果,该结果也被称为工作目录

而所谓的工作目录,其实指的就是执行文件时所在的目录,通过一行简单的代码就能验证这句话。

在src/目录下新建一个名为printing-working-dir.js的文件,编写如下代码,并在不同目录下执行:

console.log(process.cwd());

image

图3 在loading-file-with-nodejs-demo/目录下的执行结果

image

图4 在loading-file-with-nodejs-demo/src/目录下的执行结果

如截图所示,在loading-file-with-nodejs-demo/下执行文件时,其工作目录为loading-file-with-nodejs-demo/;在src/目录执行文件时,其工作目录为src/。

得到了这一重要信息之后,重新对前面的案例进行分析。

1、在loading-file-with-nodejs-demo/src/目录下执行app.js文件

此时fs.readFile("../data.json", ...)相对的是loading-file-with-nodejs-demo/src/,相对路径的上一层为loading-file-with-nodejs-demo/,该目录下存在名为data.json的文件,所以读取成功。

2、在loading-file-with-nodejs-demo/执行app.js文件

此时fs.readFile("../data.json", ...)相对的是相对的是loading-file-with-nodejs-demo/的上一层目录,demo/,该目录下不存在data.json文件,所以读取失败。

根据目前已知的信息,可得出如下结论:

使用相对路径读取文件时,如果实际的工作目录与预期的工作目录不同,就会导致抛出找不到文件的异常。

以当前代码为例,app.js中使用fs.readFile("../data.json")读取文件的预期工作目录是src/,因而,有且只有在src/下执行文件时,代码才能够正常运行;在非src/目录下都会运行失败。

# 本文代码结构

loading-file-with-nodejs-demo
├── data.json
└── src
    ├── app.js
    ├── printing-working-dir.js
    └── util
        └── common.js

三、解决方案

这种在非固定目录下执行文件(或启动系统)还挺常见的,比如说部署项目的时候,不可能说还必须要切换到某个项目目录下才能执行相应的操作,一般都在项目外层或使用脚本。

既然使用相对路径会存在问题,那就改为使用绝对路径。

或者也可以说是两者结合的方式,本文采用的是__dirname + path.resolve()。

__dirname是nodejs中的一个全局变量,指代的是当前文件所在的目录,是一个绝对路径;而path.resolve()是一个将参数解析为绝对路径的方法。

The path.resolve() method resolves a sequence of paths or path segments into an absolute path.

将src/app.js的代码修改如下,并以相同的条件执行:

const fs = require("fs");
const path = require("path");
const { print } = require("./util/common");
const data = require("../data.json");

print("require: ");
print("---");
print(data);

print("\n");

// 使用绝对、相对路径相结合代替单相对路径的方式
// path.resolve(__dirname, "../data.json")会返回相对于当前文件,data.json的路径
fs.readFile(path.resolve(__dirname, "../data.json"), { encoding: 'utf-8' }, (error, data) => {
    if (error) {
        throw error;
    }

    print("fs.readFile:");
    print("---")
    print(data);
});

image

图4 [修改后]在loading-file-with-nodejs-demo/src/目录下的执行结果

image

图5 [修改后]在loading-file-with-nodejs-demo/目录下的执行结果

使用path.resolve(__dirname, [相对路径])的意义在于,读取文件时相对的是当前文件,而不再是工作目录【process.cwd()】,使得传入fs.readFile()中的路径参数变为固定值。

因此,改动后的代码,无论在那个目录下都可以正常运行。


在nodejs中,require和fs都可以通过相对路径读取|加载文件,但两者存在差异:由于fs相对的是工作目录,并非当前文件,导致在非预期工作目录执行文件时,出现无法读取文件的错误。

即相对路径会随着工作目录的变化而变化,这种不稳定的因素正是系统异常的来源之一。

所以,一般情况下,都尽量不使用相对路径读取文件,免得出现不必要的麻烦。如本文使用__dirname + path.resolve()结合生成绝对路径的方式作为代替。

需要注意的是,这并非nodejs独有,其他语言也存在同样的问题,python、java等,想必设计者肯定有自己的理由。

以上。

评论区已关闭
返回