NodeJs的几种文件路径

上次写删除文件夹的时候用到了fs模块,也集中用到了很多种路径,当时就想写一下,在Node中使用各种路径的问题,于是就简单写了一下,可以从 这里 获取demo源代码。

刚写Node的时候经常会遇到这种情况:比如项目入口是app.js,而app.js并不一定在根目录下,比如他在bin目录下,到了启动项目时,使用node /bin/app.js和进入bin文件夹直接node app.js总会有一个起不起来,都是些路径找不到的错误,究其原因就是启动应用时执行的目录不同了,不过为什么启动脚本的位置会有这么多影响呢,让我们来总结一下。

对比常用的几种路径

Node 中的文件路径大概有 __dirname__filenameprocess.cwd()./ 或者 ../。前三个都是绝对路径,为了便于比较,./../ 我们通过 path.resolve('./')来转换为绝对路径。先看一个简单的例子。

假如我们有这样的文件结构:

B0903800-AC17-4F20-961B-7A07D13942F9

server.js 里编写如下的代码:

1
2
3
4
5
6
var path = require('path');

console.log(__dirname);
console.log(__filename);
console.log(process.cwd());
console.log(path.resolve('./'));

path-test 目录下运行 node bin/server.js 得到的结果:

A384F0C7-7DCF-49C9-9F35-4C5973245C62

进入 bin 目录下运行 node server.js 得到的结果:

B3CDC14D-1B0A-430C-A8F0-1C447A8E8981

现在我们可以总结下这几个路径的意思:

1
2
3
4
__dirname:    Nodejs的一个全局变量,获得当前执行文件所在目录的完整目录名
__filename: Nodejs的一个全局变量,获得当前执行文件的带有完整绝对路径的文件名
process.cwd():Nodejs的全局变量process的一个方法,返回当前进程的工作目录
./: 一般情况跟 process.cwd() 一样,返回 node 命令运行时所在的文件夹的绝对路径

注意__dirname得到的目录和命令执行所在的目录、__filename得到的文件名和参数指定的文件名都不一定相同,因为可能在一个文件中调用了另一个目录中的另一个文件。

更复杂的情况

我们把例子升级一下,在bin目录下新建一个test.js

1
2
3
4
5
6
7
var fs = require('fs');
require('./server.js');

fs.readFile('./server.js', function (err, data) {
if (err) return console.log(err);
console.log(data);
});

现在目录结构如下:

F8DE8DD3-DA17-4144-99B5-8012BF83ADC8

我们这次先进入bin目录执行node test.js,得到的结果:

6D160866-8F82-4F81-B05C-AB3B34F3CF86

可以看到是正常的,然后我们退出bin目录,在上一级执行node bin/test.js,得到结果:

54CF6CF2-BA04-4103-968A-1838AACD40F3

我们可以看到报错了,但是require是OK的,只是fs.readFile时路径出现了错误。从第一个例子我们可以知道,使用相对路径出现错误是预期之内的,因为在bin目录外执行时目录下已经没有server.js这个文件了,但是为什么在require中使用相对路径,就不受启动应用时执行命令目录不同的影响呢?实际上是require有自己的搜索机制,具体可以看require() 源码解读

使用path模块处理文件路径

面对复杂的路径问题,path模块可以帮你规范化连接和解析路径,还可以用于绝对路径到对路径的转换、提取路径的组成部分及判断路径是否存在等。常用的两个命令:

path.join

path.join()方法可以连接任意多个路径字符串,只是简单的连接,不会看是否真的存在。

1
2
3
4
5
var path = require('path');
//合法的字符串连接
path.join('/foo', 'bar', 'baz/asdf', 'quux', '..')
// 连接后
'/foo/bar/baz/asdf'

path.resolve

path.resolve()方法可以将多个路径解析为一个规范化的绝对路径。其处理方式类似于对这些路径逐一进行cd操作,与cd操作不同的是,这引起路径可以是文件,并且可不必实际存在(resolve()方法不会利用底层的文件系统判断路径是否存在,而只是进行路径字符串操作)。

1
path.resolve('foo/bar', '/tmp/file/', '..', 'a/../subfile')

其处理方式类型于

1
2
3
4
5
cd foo/bar
cd /tmp/file/
cd ..
cd a/../subfile
pwd

如果解析的不是绝对路径,path.resolve()会将当前工作目录(非进程工作目录)加到解析结果的前面。例如:

1
2
3
4
5
6
7
8
9
10
11
path.resolve('/foo/bar', './baz')
// 输出结果为
'/foo/bar/baz'

path.resolve('/foo/bar', '/tmp/file/')
// 输出结果为
'/tmp/file'

path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif')
// 当前的工作路径是 /Users/tyb/workspace/NodeJs/path-test/bin,则输出结果为
'/Users/tyb/workspace/NodeJs/path-test/bin/wwwroot/static_files/gif/image.gif'

两者区别

  1. join只是把各个path片段连接在一起, resolve会把‘/’当成根目录
1
2
path.join('/a', '/b')    // 返回 '/a/b'
path.resolve('/a', '/b') // 返回 '/b'
  1. join直接拼接字段,resolve解析路径,如果解析的不是绝对路径,会在前面增加当前文件所在的目录
1
2
path.join("a", "b1", "..", "b2")    // 返回 'a/b2'
path.resolve("a", "b1", "..", "b2") // 返回 '/Users/tyb/workspace/NodeJs/path-test/bin/a/b2'

相同之处

  1. 都会规范化路径
  2. 都不会去验证路径是否真的存在

总结一下

最好只在 require() 时才使用相对路径./或者../的写法,其他地方一律配合path`模块使用绝对路径。

参考链接

浅析 NodeJs 的几种文件路径

Node.js的dirname,filename,process.cwd(),./的一些坑

Node.js使用path模块处理文件路径