Skip to main content

Node.js 路径及文件操作

一、路径 path 模块

点击查看所有 path 路径模块 API

1、获取所在路径

const path = require('path');
const filepath = '/tmp/demo/js/test.js';

path.dirname(filepath) // /tmp/demo/js

2、获取文件名

const path = require('path');

path.basename('/tmp/demo/js/test.js') // test.js

path.basename('/tmp/demo/js/test/') // test

path.basename('/tmp/demo/js/test') // test

path.basename(filepath) 只是输出路径的最后一部分,不会判断是否文件名,如果只想获取文件名而不包括文件扩展名,可以用上第二个参数:

path.basename('/tmp/demo/js/test.js', '.js')  // test

3、获取文件扩展名

const path = require('path');
const filepath = '/tmp/demo/js/test.js';

path.extname(filepath) // .js

复杂文件扩展名的输出如下(下面结果用 '' 包裹,方便参考)

path.extname('index.html')  // '.html'

path.extname('index.coffee.md') // '.md'

path.extname('index.') // '.'

path.extname('index') // ''

path.extname('.index') // ''

4、路径组合

4-1、path.join([...paths])

  • path.join() 使用特定于平台的分隔符作为定界符将所有给定的 path 片段连接在一起,然后规范化生成的路径;
  • 长度为零的 path 片段会被忽略,如果连接后的路径字符串是个长度为零的字符串,则返回 '.',表示当前工作目录;
  • 如果路径中出现 ..,那么它前面的路径片段将被丢失。

举个例子:

__dirname
// __dirname 返回当前文件所在的绝对路径
const path = require('path');

path.join(__dirname, '/foo');
// /Users/leophen/work/test/foo

path.join(__dirname, './foo/bar');
// /Users/leophen/work/test/foo/bar

path.join('/foo', 'bar', '/baz/apple', 'aaa', '..');
// /foo/bar/baz/apple

path.join('foo', 'bar', 'baz');
// foo/bar/baz

path.join('foo', {}, 'bar');
// 抛出 'TypeError: Path must be a string. Received {}'

可以看到 join 方法只是拼接路径,而只有在标准的 ..../ 相对路径的时候跳出目录,而不像 resolve 那样遇见 / 就直接把整个路径替换。

4-2、path.resolve([...paths])

  • path.resolve() 方法将路径或路径片段的序列解析为一个绝对路径;
  • 给定的路径序列从右到左处理,每个后续的 path 会被依次解析,直到构造成一个绝对路径。例如,给定路径片段的序列:/foo、/bar、baz,调用 path.resolve('/foo', '/bar', 'baz') 将返回 /bar/baz,因为 'baz' 不是绝对路径,而 '/bar' + '/' + 'baz' 是;
  • 如果在处理完所有给定的 path 片段后,还没有生成绝对路径,则会直接使用当前工作目录;
  • 生成的路径被规范化,末尾的斜杠会被删除(除非路径解析为根目录)
  • 长度为零的 path 片段被忽略;
  • 如果没有传入 path 片段,则 path.resolve() 将返回当前工作目录的绝对路径。

举个例子:

const path = require('path');

// 假设当前工作路径是 ↓
// /Users/a/Documents/examples/temp

console.log(path.resolve(''));
// /Users/a/Documents/examples/temp

console.log(path.resolve('.'));
// /Users/a/Documents/examples/temp

console.log(path.resolve('/foo/bar', './baz'));
// /foo/bar/baz

console.log(path.resolve('/foo/bar', './baz/'));
// /foo/bar/baz

console.log(path.resolve('/foo/bar', '/tmp/file/'));
// /tmp/file

console.log(path.resolve('www', 'js/upload', '../mod.js'));
// /Users/a/Documents/examples/temp/www/js/mod.js

可以看到,返回值以最后一次出现 / 根路径的值为当前路径的开始。

4-3、join 与 resolve 的区别

join 是对路径片段进行拼接,规范化生成一个路径;而 resolve 则把 '/' 当成根目录,生成一个绝对路径:

path.join('/a', '/b');    // /a/b
path.resolve('/a', '/b'); // /b

resolve 在传入非/路径时,会自动加上当前目录形成一个绝对路径,而 join 仅仅用于路径拼接:

// 当前路径为:/Users/leophen/work/test

path.join('a', 'b', '..', 'd'); // a/d
path.resolve('a', 'b', '..', 'd'); // /Users/leophen/work/test/a/d

可以看出 resolve 在传入的第一参数为非根路径时,会返回一个带当前目录路径的绝对路径。

二、文件 fs 模块

点击查看所有 fs 文件模块 API

1、读取文件

1-1、普通读取

const fs = require('fs')

fs.readFile('./temp.txt', (err, data) => {
if (err) {
console.error(err)
return
}
// data 是二进制格式,转为字符串输出便于查看
console.log(data.toString())
})

1-2、通过 Stream 文件流读取

当内存中无法一次装下需要处理的数据,或一边读取一边处理更加高效时,就需要用到 Stream 数据流。

test.js
const fs = require('fs');
const readStream = fs.createReadStream('./temp.txt');

readStream
.on('data', (chunk) => {
console.log(chunk.toString());
})
.on('error', (err) => {
console.log('出错: ' + err.message);
})
.on('end', () => { // 没有数据了
console.log('没有数据了');
})
.on('close', () => { // 已经关闭,不会再有事件抛出
console.log('已经关闭');
});

执行结果如下:

2、写入文件

2-1、普通写入

const fs = require('fs')

const content = '\n写入的新内容\n'
const option = {
flag: 'a' // 追加写入用 a,完全覆盖用 w
}
fs.writeFile('./temp.txt', content, option, (err) => {
if (err) {
console.error(err)
}
})

2-2、通过 Stream 文件流写入

const fs = require('fs');
const writeStream = fs.createWriteStream('./temp.txt');

writeStream
.on('close', function () { // 已经关闭,不会再有事件抛出
console.log('已经关闭');
});

writeStream.write('hello');
writeStream.write(' world');
writeStream.end('');

// 执行完 temp.txt 的内容为:hello world

2-3、实现文件拷贝

const fs = require('fs');
const readStream = fs.createReadStream('./temp.txt');
const writeStream = fs.createWriteStream('./temp_copy.txt');

// 通过 pipe 管道进行拷贝,相当于执行完所有 .on('data', (chunk) => ...
readStream.pipe(writeStream)

readStream
.on('end', () => {
console.log('拷贝完成');
});

3、判断文件是否存在

const fs = require('fs')

fs.exists('./temp.txt', (res) => {
console.log(res) // 存在返回 true,不存在返回 false
})

注意,exists 方法已被废弃,判断文件是否存在可以通过以下方式:

const fs = require('fs')

fs.access('./temp.txt', (err) => {
if (err) throw err;
console.log('文件存在');
})

access 除了可以判断一个文件是否存在,还可以判断是否可读、是否可写:

// 检查文件是否存在于当前目录中
fs.access('./temp.txt', fs.constants.F_OK, (err) => {
console.log(`${err ? '不存在' : '存在'}`);
});

// 检查文件是否可读
fs.access('./temp.txt', fs.constants.R_OK, (err) => {
console.log(`${err ? '不可读' : '可读'}`);
});

// 检查文件是否可写
fs.access('./temp.txt', fs.constants.W_OK, (err) => {
console.log(`${err ? '不可写' : '可写'}`);
});

// 检查文件是否存在于当前目录中、以及是否可写
fs.access('./temp.txt', fs.constants.F_OK | fs.constants.W_OK, (err) => {
if (err) {
console.error(
`${err.code === 'ENOENT' ? '不存在' : '只可读'}`);
} else {
console.log(`存在,且可写`);
}
});

4、文件重命名

const fs = require('fs')

fs.rename('./temp.txt', './temp1.txt', (err) => {
if (err) throw err;
console.log('重命名成功');
});

5、删除文件

const fs = require('fs')

fs.unlink('./temp.txt', (err) => {
if (err) throw err;
console.log('文件删除成功');
});

点击查看更多路径及文件操作详情