文件属性

每个文件都带有一组详细信息,可以使用 Node.js 进行检查。
具体地说,使用fs模块提供的stat()方法。
调用时传入文件的路径,一旦 Node.js 获得文件的详细信息,则会调用传入的回调函数,并带上两个参数:错误消息和文件属性:

  1. const fs = require('fs')
  2. fs.stat('/Users/joe/test.txt', (err, stats) => {
  3. if (err) {
  4. console.error(err)
  5. return
  6. }
  7. //可以访问 `stats` 中的文件属性
  8. })

Node.js 也提供了同步的方法,该方法会阻塞线程,直到文件属性准备就绪为止:

  1. const fs = require('fs')
  2. try {
  3. const stats = fs.statSync('/Users/joe/test.txt')
  4. } catch (err) {
  5. console.error(err)
  6. }

文件的信息包含在属性变量中。 可以通过属性提取哪些信息?
很多,包括:

  • 使用stats.isFile()和stats.isDirectory()判断文件是否目录或文件。
  • 使用stats.isSymbolicLink()判断文件是否符号链接。
  • 使用stats.size获取文件的大小(以字节为单位)。

还有其他一些高级的方法,但是在日常编程中会使用的大部分是这些。

  1. const fs = require('fs')
  2. fs.stat('/Users/joe/test.txt', (err, stats) => {
  3. if (err) {
  4. console.error(err)
  5. return
  6. }
  7. stats.isFile() //true
  8. stats.isDirectory() //false
  9. stats.isSymbolicLink() //false
  10. stats.size //1024000 //= 1MB
  11. })

文件路径

系统中的每个文件都有路径。
在 Linux 和 macOS 上,路径可能类似于:
/users/joe/file.txt
在 Windows 上则有所不同,具有类似以下的结构:
C:\users\joe\file.txt
当在应用程序中使用路径时需要注意,因为必须考虑到这种差异。
可以使用以下方式将此模块引入到文件中:

  1. const path = require('path');

现在可以开始使用其方法。

从路径中获取信息

给定一个路径,可以使用以下方法从其中提取信息:

  • dirname: 获取文件的父文件夹。
  • basename: 获取文件名部分。
  • extname: 获取文件的扩展名。

例如:

  1. const notes = '/users/joe/notes.txt'
  2. path.dirname(notes) // /users/joe
  3. path.basename(notes) // notes.txt
  4. path.extname(notes) // .txt
  5. 可以通过为basename指定第二个参数来获取不带扩展名的文件名:
  6. path.basename(notes, path.extname(notes)) //notes

使用路径

可以使用path.join()连接路径的两个或多个片段:参数是字符串

  1. const name = 'joe'
  2. path.join('/', 'users', name, 'notes.txt') //'/users/joe/notes.txt'

可以使用path.resolve()获得相对路径的绝对路径计算:

  1. path.resolve('joe.txt') //'/Users/joe/joe.txt' 如果从主文件夹运行。

在此示例中,Node.js 只是简单地将/joe.txt附加到当前工作目录。
如果指定第二个文件夹参数,则resolve会使用第一个作为第二个的基础:

  1. path.resolve('tmp', 'joe.txt') //'/Users/joe/tmp/joe.txt' 如果从主文件夹运行。

如果第一个参数以斜杠开头,则表示它是绝对路径:

  1. path.resolve('/etc', 'joe.txt') //'/etc/joe.txt'

path.normalize()是另一个有用的函数,当包含诸如.、..或双斜杠之类的相对说明符时,其会尝试计算实际的路径:

  1. path.normalize('/users/joe/..//test.txt') //'/users/test.txt'

解析和规范化都不会检查路径是否存在。 其只是根据获得的信息来计算路径。

resolve vs join 区别

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

~表示为 home 目录 的意思,.则是表示目前所在的目录,..则表示目前目录位置的上一层目录 cd /usr/bin 跳到 /usr/bin 如果当前所在位置为 /pro/app 则 cd ./bin 位置为 pro/app/bin -pro -app -bin

  1. path.resolve('foo/bar', '/tmp/file/', '..', 'a/../subfile')
  2. 相当于
  3. cd foo/bar
  4. cd /tmp/file/
  5. cd ..
  6. cd a/../subfile
  7. pwd
  1. path.resolve('/foo/bar', './baz')
  2. // 输出结果为
  3. '/foo/bar/baz'
  4. path.resolve('/foo/bar', '/tmp/file/')
  5. // 输出结果为
  6. '/tmp/file'
  7. path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif')
  8. // 当前的工作路径是 /home/itbilu/node,则输出结果为
  9. '/home/itbilu/node/wwwroot/static_files/gif/image.gif'
  1. const path = require('path');
  2. let myPath = path.join(__dirname,'/img/so'); //D:\myProgram\test\img\so
  3. let myPath2 = path.join(__dirname,'./img/so'); //D:\myProgram\test\img\so
  4. let myPath3 = path.resolve(__dirname,'/img/so'); // D:\img\so
  5. let myPath4 = path.resolve(__dirname,'./img/so'); // D:\myProgram\test\img\so
  6. console.log(__dirname); //D:\myProgram\test

路径迷惑

Node.js中的文件路径大概有 dirname, filename, process.cwd(), ./ 或者 ../,前三个都是绝对路径,为了便于比较,./ 和 ../ 我们通过 path.resolve(‘./‘)来转换为绝对路径。
简单说一下这几个路径的意思,:

  1. __dirname 获得当前执行文件所在目录的完整目录名
  2. __filename 获得当前执行文件的带有完整绝对路径的文件名
  3. process.cwd():获得当前执行node命令时候的文件夹目录名
  4. ./: 文件所在目录

先看一看我电脑当前的目录结构:

  1. syntax/
  2. -nodejs/
  3. -1.findLargest.js
  4. -2.path.js
  5. -3.fs.js
  6. -regs
  7. -regx.js
  8. -test.txt

在 path.js 里面我们写这些代码,看看输出是什么:

  1. const path = require('path')
  2. console.log('__dirname:', __dirname)
  3. console.log('__filename:', __filename)
  4. console.log('process.cwd():', process.cwd())
  5. console.log('./:', path.resolve('./'))

在当前目录下也就是nodejs目录运行node path.js,我们看看输出结果:

  1. __dirname /Users/jawil/Desktop/nodejs/demo/ES6-lottery/syntax/nodejs
  2. __filename /Users/jawil/Desktop/nodejs/demo/ES6-lottery/syntax/nodejs/2.path.js
  3. process.cwd(): /Users/jawil/Desktop/nodejs/demo/ES6-lottery/syntax/nodejs
  4. ./: /Users/jawil/Desktop/nodejs/demo/ES6-lottery/syntax/nodejs

参考
路径require的坑

文件读写

读文件

在 Node.js 中读取文件最简单的方式是使用fs.readFile()【同步的版本fs.readFileSync()】方法,向其传入文件路径、编码、以及会带上文件数据(以及错误)进行调用的回调函数:

  1. const fs = require('fs')
  2. fs.readFile('/Users/joe/test.txt', 'utf8' , (err, data) => {
  3. if (err) {
  4. console.error(err)
  5. return
  6. }
  7. console.log(data)
  8. })

另外,也可以使用
fs.readFile()和fs.readFileSync()都会在返回数据之前将文件的全部内容读取到内存中。
这意味着大文件会对内存的消耗和程序执行的速度产生重大的影响。
在这种情况下,更好的选择是使用流来读取文件的内容。

写文件

在 Node.js 中写入文件最简单的方式是使用fs.writeFile()API。
例如:

  1. const fs = require('fs')
  2. const content = '一些内容'
  3. fs.writeFile('/Users/joe/test.txt', content, err => {
  4. if (err) {
  5. console.error(err)
  6. return
  7. }
  8. //文件写入成功。
  9. })

另外,也可以使用同步的版本fs.writeFileSync():

  1. const fs = require('fs')
  2. const content = '一些内容'
  3. try {
  4. const data = fs.writeFileSync('/Users/joe/test.txt', content)
  5. //文件写入成功。
  6. } catch (err) {
  7. console.error(err)
  8. }

默认情况下,此 API 会替换文件的内容(如果文件已经存在)。
可以通过指定标志来修改默认的行为:
fs.writeFile(‘/Users/joe/test.txt’, content, { flag: ‘a+’ }, err => {})
可能会使用的标志有:

  • r+打开文件用于读写。
  • w+打开文件用于读写,将流定位到文件的开头。如果文件不存在则创建文件。
  • a打开文件用于写入,将流定位到文件的末尾。如果文件不存在则创建文件。
  • a+打开文件用于读写,将流定位到文件的末尾。如果文件不存在则创建文件。

(可以在http://nodejs.cn/api/fs.html#fs_file_system_flags中查看更多标志)

追加到文件

将内容追加到文件末尾的便捷方法是fs.appendFile()(及其对应的fs.appendFileSync()):

  1. const content = '一些内容'
  2. fs.appendFile('file.log', content, err => {
  3. if (err) {
  4. console.error(err)
  5. return
  6. }
  7. //完成!
  8. })

使用流

所有这些方法都是在将全部内容写入文件之后才会将控制权返回给程序(在异步的版本中,这意味着执行回调)。
在这种情况下,更好的选择是使用流写入文件的内容。

文件夹

Node.js 的fs核心模块提供了许多便捷的方法用于处理文件夹。

检查文件夹是否存在

使用fs.access()检查文件夹是否存在以及 Node.js 是否具有访问权限。

创建新的文件夹

使用fs.mkdir()或fs.mkdirSync()可以创建新的文件夹。

  1. const fs = require('fs')
  2. const folderName = '/Users/guan/Desktop/node-upload/test';
  3. try {
  4. if (!fs.existsSync(folderName)) {
  5. fs.mkdirSync(folderName)
  6. }
  7. } catch (err) {
  8. console.error(err)
  9. }

读取目录的内容

使用fs.readdir()或fs.readdirSync()可以读取目录的内容。
这段代码会读取文件夹的内容(全部的文件和子文件夹),并返回它们的相对路径:

  1. const fs = require('fs')
  2. const path = require('path')
  3. const folderPath = '/Users/guan/Desktop/node-upload';
  4. fs.readdirSync(folderPath)
  5. [ 'index.css',
  6. 'index.js',
  7. 'node_modules',
  8. 'package-lock.json',
  9. 'package.json',
  10. 'static',
  11. 'test',
  12. 'test.txt',
  13. '程序是怎样跑起来的.pdf' ]

可以获取完整的路径:

  1. fs.readdirSync(folderPath).map(fileName => {
  2. return path.join(folderPath, fileName)
  3. })
  4. /Users/guan/Desktop/node-upload/index.css
  5. /Users/guan/Desktop/node-upload/index.js
  6. /Users/guan/Desktop/node-upload/node_modules
  7. /Users/guan/Desktop/node-upload/package-lock.json
  8. /Users/guan/Desktop/node-upload/package.json
  9. /Users/guan/Desktop/node-upload/static
  10. /Users/guan/Desktop/node-upload/test
  11. /Users/guan/Desktop/node-upload/test.txt
  12. /Users/guan/Desktop/node-upload/程序是怎样跑起来的.pdf

也可以过滤结果以仅返回文件(排除文件夹):

  1. const isFile = fileName => {
  2. return fs.lstatSync(fileName).isFile()
  3. }
  4. fs.readdirSync(folderPath).map(fileName => {
  5. return path.join(folderPath, fileName)
  6. })
  7. .filter(isFile)

重命名文件夹

使用fs.rename()或fs.renameSync()可以重命名文件夹。 第一个参数是当前的路径,第二个参数是新的路径:

  1. const fs = require('fs')
  2. fs.rename('/Users/joe', '/Users/roger', err => {
  3. if (err) {
  4. console.error(err)
  5. return
  6. }
  7. //完成
  8. })
  9. fs.renameSync()是同步的版本:
  10. const fs = require('fs')
  11. try {
  12. fs.renameSync('/Users/joe', '/Users/roger')
  13. } catch (err) {
  14. console.error(err)
  15. }

删除文件夹

使用fs.rmdir()或fs.rmdirSync()可以删除文件夹。
删除包含内容的文件夹可能会更复杂。
在这种情况下,最好安装fs-extra模块,该模块非常受欢迎且维护良好。 它是fs模块的直接替代品,在其之上提供了更多的功能。
在此示例中,需要的是remove()方法。
使用以下命令安装:
npm install fs-extra
并像这样使用它:

  1. const fs = require('fs-extra')
  2. const folder = '/Users/joe'
  3. fs.remove(folder, err => {
  4. console.error(err)
  5. })

也可以与 promise 一起使用:

  1. fs.remove(folder)
  2. .then(() => {
  3. //完成
  4. })
  5. .catch(err => {
  6. console.error(err)
  7. })

或使用 async/await:

  1. async function removeFolder(folder) {
  2. try {
  3. await fs.remove(folder)
  4. //完成
  5. } catch (err) {
  6. console.error(err)
  7. }
  8. }
  9. const folder = '/Users/joe'
  10. removeFolder(folder)
  1. fs.access(): 检查文件是否存在,以及 Node.js 是否有权限访问。
  2. fs.appendFile(): 追加数据到文件。如果文件不存在,则创建文件。
  3. fs.chmod(): 更改文件(通过传入的文件名指定)的权限。相关方法:fs.lchmod()、fs.fchmod()。
  4. fs.chown(): 更改文件(通过传入的文件名指定)的所有者和群组。相关方法:fs.fchown()、fs.lchown()。
  5. fs.close(): 关闭文件描述符。
  6. fs.copyFile(): 拷贝文件。
  7. fs.createReadStream(): 创建可读的文件流。
  8. fs.createWriteStream(): 创建可写的文件流。
  9. fs.link(): 新建指向文件的硬链接。
  10. fs.mkdir(): 新建文件夹。
  11. fs.mkdtemp(): 创建临时目录。
  12. fs.open(): 设置文件模式。
  13. fs.readdir(): 读取目录的内容。
  14. fs.readFile(): 读取文件的内容。相关方法:fs.read()。
  15. fs.readlink(): 读取符号链接的值。
  16. fs.realpath(): 将相对的文件路径指针(.、..)解析为完整的路径。
  17. fs.rename(): 重命名文件或文件夹。
  18. fs.rmdir(): 删除文件夹。
  19. fs.stat(): 返回文件(通过传入的文件名指定)的状态。相关方法:fs.fstat()、fs.lstat()。
  20. fs.symlink(): 新建文件的符号链接。
  21. fs.truncate(): 将传递的文件名标识的文件截断为指定的长度。相关方法:fs.ftruncate()。
  22. fs.unlink(): 删除文件或符号链接。
  23. fs.unwatchFile(): 停止监视文件上的更改。
  24. fs.utimes(): 更改文件(通过传入的文件名指定)的时间戳。相关方法:fs.futimes()。
  25. fs.watchFile(): 开始监视文件上的更改。相关方法:fs.watch()。
  26. fs.writeFile(): 将数据写入文件。相关方法:fs.write()。

路径

path模块提供了许多非常实用的函数来访问文件系统并与文件系统进行交互。
无需安装。 作为 Node.js 核心的组成部分,可以通过简单地引用来使用它:

  1. const path = require('path')

该模块提供了path.sep(作为路径段分隔符,在 Windows 上是\,在 Linux/macOS 上是/)
path.delimiter(作为路径定界符,在 Windows 上是;,在 Linux/macOS 上是:)。
还有这些path方法:

path.basename()

返回路径的最后一部分。 第二个参数可以过滤掉文件的扩展名:

  1. require('path').basename('/test/something') //something
  2. require('path').basename('/test/something.txt') //something.txt
  3. require('path').basename('/test/something.txt', '.txt') //something

path.dirname()

返回路径的目录部分:

  1. require('path').dirname('/test/something') // /test
  2. require('path').dirname('/test/something/file.txt') // /test/something

path.extname()

返回路径的扩展名部分。

  1. require('path').extname('/test/something') // ''
  2. require('path').extname('/test/something/file.txt') // '.txt'

path.isAbsolute()

如果是绝对路径,则返回 true。

  1. require('path').isAbsolute('/test/something') // true
  2. require('path').isAbsolute('./test/something') // false

path.join()

连接路径的两个或多个部分:

  1. const name = 'joe'
  2. require('path').join('/', 'users', name, 'notes.txt') //'/users/joe/notes.txt'

path.normalize()

当包含类似.、..或双斜杠等相对的说明符时,则尝试计算实际的路径:

  1. require('path').normalize('/users/joe/..//test.txt') //'/users/test.txt'

path.parse()

解析对象的路径为组成其的片段:

  • root: 根路径。
  • dir: 从根路径开始的文件夹路径。
  • base: 文件名 + 扩展名
  • name: 文件名
  • ext: 文件扩展名

例如:
require(‘path’).parse(‘/users/test.txt’)
结果是:
{
root: ‘/‘,
dir: ‘/users’,
base: ‘test.txt’,
ext: ‘.txt’,
name: ‘test’
}

path.relative()

接受 2 个路径作为参数。 基于当前工作目录,返回从第一个路径到第二个路径的相对路径。
例如:

  1. require('path').relative('/Users/joe', '/Users/joe/test.txt') //'test.txt'
  2. require('path').relative('/Users/joe', '/Users/joe/something/test.txt') //'something/test.txt'

path.resolve()

可以使用path.resolve()获得相对路径的绝对路径计算:

  1. path.resolve('joe.txt') //'/Users/joe/joe.txt' 如果从主文件夹运行

通过指定第二个参数,resolve会使用第一个参数作为第二个参数的基准:

  1. path.resolve('tmp', 'joe.txt') //'/Users/joe/tmp/joe.txt' 如果从主文件夹运行

如果第一个参数以斜杠开头,则表示它是绝对路径:

  1. path.resolve('/etc', 'joe.txt') //'/etc/joe.txt'