File descriptor 文件描述器

当需要访问文件系统里的某个文件时,需要首先得到这个文件的 file descriptor 文件描述器。

一个 file descriptor 就是通过 fs 模块的 open() method 打开对应文件的返回数据。它的结构如下:

const fs = require('fs')

fs.open('./test.txt', 'r', (err, data) => {
    if (err) {
        console.log('open fail')
        return
    }
    console.log('open success')
})

如果文件打开成功,callback 中的 data 数据就是一个 file descriptor。注意并不是文件的内容。

这种方式是异步处理的,文件打开过程中系统会执行其他任务。

在上面的示例中,fs.open() 的第三个参数使用了 'f' 标记,它的意思就是以只读模式打开文件。我们可以通过使用不同的标记来以不同的方式打开文件:

  • r 只读模式,文件不存在会报错
  • a 只写模式,streaming 流定位到文件结尾位置,如果文件不存在则创建文件
  • r+ 读写模式,文件不存在会报错
  • w+ 读写模式,streaming 流定位到文件起始位置,如果文件不存在则创建文件
  • a+ 读写模式,streaming 流定位到文件结尾位置,如果文件不存在则创建文件

更多 flag 的使用参考:https://nodejs.org/api/fs.html#fs_file_system_flags

也可以使用 fs.openSync() method 打开文件,其返回值为 file descriptor 而不是通过 callback 的方式:

try {
    const data = fs.openSync('./test.txt', 'r')
    console.log('open success')
} catch (error) {
    console.log('open fail')
}

这种方式是同步模式的,程序会等待接收到 file descriptor 或者 error 后才执行后面的指令。

File stats 文件信息

每个文件都包含独自的属性信息,可以通过 node.js 查看。通常我们使用 fs 模块的 stat() method。

以下是一个简单示例:

const fs = require('fs')

fs.stat('./test.txt', (err, stats) => {
    if (err) {
        console.log('read fail')
        return
    }
    console.log(stats)
})

同样的,node.js 也提供了同步模式 method,在读取 stats 过程中会阻塞进程知道读取结束:

try {
    const stats = fs.statSync('./test.txt')
    console.log(stats)
} catch (error) {
    console.log(error)
}

文件信息存储在 stats 变量中,可以读取其中的需要的信息,常用的有以下:

  • 是否是文件或文件夹:stats.isFile()stats.isDirectory()
  • 是否是一个链接文件:stats.isSymbolicLink()
  • 文件大小(单位为字节):stats.size

File Path 文件路径

每个文件在系统中都有一个路径。在 Linux 中路径格式如:/users/joe/file.txt,在 Windows 中路径格式如:C:\users\joe\file.txt。在程序中引用路径时需要特别注意路径的格式。

node.js 中可以使用 path 模块来处理文件路径相关数据。

如果给定一个路径,可以提取其相关数据,如:

  • path.dirname: 获取文件的上级文件夹路径
  • path.basename: 获取文件名部分
  • path.extname: 获取文件扩展名
const path = require('path')

const file = '/home/marco/file.txt'
console.log(path.dirname(file))
console.log(path.basename(file))
console.log(path.extname(file))

//output:
/home/marco
file.txt
.txt

可以获取文件名不包含扩展名部分,通过给 path.basename() 设置第二个参数:

console.log(path.basename(file, path.extname(file)))

//output:
file

可以组合多个部分为一个 path:

const dir = 'home/marco/'
p = path.join('/' + dir + 'test.txt')
console.log(p)

//output:
/home/marco/test.txt

使用 path.resolve() 可以从一个相对路径获取到绝对路径:

p = path.resolve('file.txt')
console.log(p)
p = path.resolve('./test/file.txt')
console.log(p)

//OUTPUT:
/mnt/hgfs/Development/node.js/filesystem/file.txt
/mnt/hgfs/Development/node.js/filesystem/test/file.txt

以上示例中,会将程序当前执行路径作为绝对路径添加给后面定义的文件相对路径。

也可以将文件的相对目录单独定义,会自动组合它们:

p = path.resolve('tmp', 'file.txt')
console.log(p)

//output:
/mnt/hgfs/Development/node.js/filesystem/tmp/file.txt

如果在路径前加斜杠/ 表明这就是一个绝对路径:

p = path.resolve('/tmp', 'file.txt')
console.log(p)

//output:
/tmp/file.txt

如果路径中包含相对关系标记符如: .., //等,可以使用 path.normalize() 得到常规形式的路径:

p = path.normalize('/home/../test/file.txt')
console.log(p)

//output:
/test/file.txt

path.resolve 和 path.normalize 都不会检查路径是否真实存在,它们仅仅是根据提供的数据计算路径结果。

reading file 读取文件

最简单的方式读取文件内容就是通过 fs.readFile() method,需要给它传入文件路径,编码格式,callback function:

const fs = require('fs')

fs.readFile('./test.txt', 'utf-8', (err, data) => {
    if (err) {
        console.log('read fail')
        return
    }
    console.log(data)
})

或者也可以使用同步模式的 fs.readFileSync()

try {
    data = fs.readFileSync('./test.txt', 'utf-8')
    console.log(data)
} catch (error) {
    console.log('read error')
}

fs.readFile()fs.readFileSync() 都会先将文件内容读取到内存中,然后返回数据。这就意味着读取大文件会影响系统内存的占用量,所以一个比较好的选择是使用 stream 流读取文件内容。后续章节会介绍 stream 模块。

writing file 文件写入

最简单的写入文件的方法就是使用 fs.writeFile() method。

示例如下:

const fs = require('fs')

const content = 'some new words'
fs.writeFile('./test.txt', content, err => {
    if (err) {
        console.log('write error')
        return
    }
    console.log('write success')
})

或者使用同步模式的版本 fs.writeFileSync()

try {
    fs.writeFileSync('./test.txt', content)
    console.log('write success')
} catch (error) {
    console.log('write error')
}

默认情况下,如果这个文件已经存在,API 会替换掉这个文件中已有的内容。我们可以通过定义 flag 来修改这个设置:

fs.writeFile('./test.txt', content, {flag: 'a+'}, err => {
    if (err) {
        console.log('write error')
        return
    }
    console.log('write success')
})

flag 的定义和上面的 fs.open 中定义的 flag 一样,可以参考设置。

append to a file 给文件添加内容

给已有文件附加内容更加方便的方法是使用 fs.appendFile()fs.appendFileSync(),例如:

const fs = require('fs')

content = '\nnew line\n'
fs.appendFile('./test.txt', content, err => {
    if (err) {
        console.log('write error')
        return
    }
    console.log('write success')
})

上面了方法都会在将数据完全写入文件后在执行 callback,这种情况下使用 stream 是一个更好的方法。

working with folder 文件夹操作

node.js 的 fs 模块提供了很多实用的 method 来对文件夹进行操作。

检查文件夹是否存在

使用 fs.access() 可以检查文件夹是否存在,以及 node.js 是否有访问它的权限:

const fs = require('fs')

fs.access('./tmp', err => {
    console.log(err ? 'not exist' : 'exist')
})

//output:
//not exist

创建新文件夹

使用 fs.mkdir()fs.mkdirSync() 创建新文件夹:

const fs = require('fs')

const fd = 'tmp'
try {
    if (!fs.existsSync(fd)) {
        fs.mkdirSync(fd)
        console.log('create success')
    }
} catch (error) {
    console.log('create fail')
}

上面使用了同步模式 method 创建文件夹,看起来更加直观。

read content of a directory 读取目录内容

使用 fs.readdir()fs.readdirSync() 可以读取某个目录中的内容,包括其中的文件和文件夹。返回值为一个数组:

const fs = require('fs')

const p = '/usr'
const d = fs.readdirSync(p)
console.log(d)

输出为:

[
  'bin',     'games',
  'include', 'lib',
  'lib32',   'lib64',
  'libexec', 'libx32',
  'local',   'sbin',
  'share',   'src'
]

通过 path 模块的 joinresolve method 可以输出每个元素的绝对路径,修改以上示例:

const fs = require('fs')
const path = require('path')

const p = '/usr'
const d = fs.readdirSync(p).map(f => {
    return path.resolve(p, f)
})
console.log(d)

输出如下:

[
  '/usr/bin',     '/usr/games',
  '/usr/include', '/usr/lib',
  '/usr/lib32',   '/usr/lib64',
  '/usr/libexec', '/usr/libx32',
  '/usr/local',   '/usr/sbin',
  '/usr/share',   '/usr/src'
]

如果只想获取目录下的文件而不包含文件夹,可以定义一个 filter 过滤元素:

const fs = require('fs')
const path = require('path')

const isFile = fileName => {
    return fs.statSync(fileName).isFile()
}
const p = '/usr'
const d = fs.readdirSync(p).map(f => {
    return path.resolve(p, f)
}).filter(isFile)

console.log(d)

rename folder 重命名文件夹

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

const fs = require('fs')

fs.rename('./tmp', './new', err => {
    if (err) {
        console.log('rename error')
        return
    }
    console.log('rename success')
})

remove folder 删除文件夹

使用 fs.rmdir()fs.rmdirSync() 可以删除文件夹。

但删除文件夹相比其他操作稍微复杂,用到的工具可能超出你需要的部分。所以最简单的方法就是使用第三方模块:fs-extra 来处理。它是 fs 的替代品,在 fs 模块的基础上提供了很多新功能。

使用 fs-extraremove() method 可以实现删除文件夹的功能:

const fs = require('fs-extra')

const p = './new'
fs.remove(p, err => {
    if (err) {
        console.log('remove error')
        return
    }
    console.log('remove success')
})

也可以使用 promise 模式:

fs.remove(p)
.then(() => console.log('remove success'))
.catch((err) => console.log(err))

还可以使用 async/await 模式:

const rm = async (p) => {
    if (!fs.existsSync(p)) {
        console.log('folder does not exist')
        return
    }
    try {
        await fs.remove(p)
        console.log('remove success')
    } catch (error) {
        console.log('remove error')
    }
}
rm(p)

下一章我们继续了解 fs 和 path 模块的详细内容。

标签:无

你的评论