Node.js 路径及文件操作
一、路径 path 模块
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 模块
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('已经关闭');
});
temp.txt
111
222
333
执行结果如下:
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('文件删除成功');
});