JavaScript 长期以来一直是最流行的脚本语言之一,但在很长一段时间内,它并不是服务器端后端应用程序开发的最佳选择。后来出现了 Node.js,它用于创建使用 JavaScript 编程语言构建的服务器端、事件驱动的轻量级应用程序。
Node.js 是一种开源 JavaScript 运行时,可在任何顶级操作系统(Windows、Mac、Linux)上免费下载和安装。近年来,Node.js 越来越受到应用程序创建者的青睐,也为寻求一技之长的 JavaScript 开发人员提供了许多新的就业机会。
在本文中,我们将学习使用 Node.js 管理文件系统。使用 Node.js API 与文件系统交互并执行许多复杂的操作并不费力,了解如何使用这些 API 将提高您的工作效率。
了解 Node.js 文件系统的先决条件
首要前提是在操作系统上安装 Node.js。Node.js 的运行不需要任何复杂的硬件,因此很容易下载并在计算机上安装 Node.js。
如果您还具备 JavaScript 的基础知识,将有助于处理 Node.js 模块,如文件系统(也称为 “FS “或 “fs”)。对 JavaScript 函数、回调函数和承诺的高级理解将帮助您更快地掌握这一主题。
Node.js 文件系统模块
处理文件和目录是全栈应用程序的基本需求之一。您的用户可能希望将图片、简历或其他文件上传到服务器。同时,您的应用程序可能需要读取配置文件、移动文件,甚至以编程方式更改文件权限。
Node.js 文件系统模块涵盖了所有这些功能。它提供了多个与文件系统无缝交互的 API。大多数 API 都可通过选项和标志进行自定义。您还可以使用它们执行同步和异步文件操作。
在深入了解文件系统模块之前,让我们先来了解一下 Node.js 模块。
Node.js 模块
Node.js 模块是一组作为 API 提供给用户程序使用的功能。例如,你可以使用 fs
模块与文件系统交互。同样,http
模块利用其功能创建服务器和进行更多操作。Node.js 提供了大量模块,可为您抽象出许多底层功能。
您也可以制作自己的模块。在 Node.js 14 及以后的版本中,您可以通过两种方式创建和使用 Node.js 模块: CommonJS (CJS) 和 ESM (MJS) 模块。我们将在本文中看到的所有示例都采用 CJS 风格。
在 Node.js 中处理文件
处理文件涉及对文件和目录(文件夹)的各种操作。现在,我们将通过示例源代码了解这些操作中的每一种。请打开您最喜欢的源代码编辑器,边读边试。
首先,在源文件中导入 fs
模块,以便开始使用文件系统方法。在 CJS 风格中,我们使用 require
方法从模块中导入方法。因此,要导入并使用 fs 模块方法,可以这样做
const { writeFile } = require('fs/promises');
另外,请注意我们从 fs/promises
包中导入了 writeFile
方法。我们希望使用 promisified 方法,因为它们是最新的方法,而且易于使用 async/await 关键字和较少的代码。其他替代方法有同步方法和回调函数,我们稍后会看到。
如何创建和写入文件
创建和写入文件有三种方法:
- 使用
writeFile
方法 - 使用
appendFile
方法 - 使用
openFile
方法
这些方法接受文件路径和数据作为要写入文件的内容。如果文件存在,它们会替换文件中的内容。否则,它们会用这些内容创建一个新文件。
1. 使用 writeFile 方法
下面的代码片段展示了 writeFile
方法的用法。首先,使用下面的代码片段创建一个名为 createFile.js 的文件:
const { writeFile } = require('fs/promises'); async function writeToFile(fileName, data) { try { await writeFile(fileName, data); console.log(`Wrote data to ${fileName}`); } catch (error) { console.error(`Got an error trying to write the file: ${error.message}`); } }
请注意,我们使用 await
关键字来调用该方法,因为它会返回一个 JavaScript 承诺。成功的承诺将创建/写入文件。我们有一个 try-catch 块来处理承诺被拒绝时的错误。
现在我们可以调用 writeToFile
函数了:
writeToFile('friends.txt', 'Bob');
然后,打开命令提示符或终端,使用以下命令运行上述程序:
node createFile.js
它将创建一个名为 friends.txt 的新文件,其中一行简单地写道:
Bob
2. 使用 appendFile 方法
顾名思义,该方法的主要用途是追加和编辑文件。不过,你也可以使用相同的方法来创建文件。
请看下面的函数。我们使用带有 w
标志的 appendFile
方法来写入文件。追加文件的默认标志是 a
:
const { appendFile} = require('fs/promises'); async function appendToFile(fileName, data) { try { await appendFile(fileName, data, { flag: 'w' }); console.log(`Appended data to ${fileName}`); } catch (error) { console.error(`Got an error trying to append the file: {error.message}`); } }
现在,您可以像这样调用上述函数:
appendToFile('activities.txt', 'Skiing');
接下来,您可以在 Node.js 环境中使用 node 命令执行代码,就像我们之前看到的那样。这将创建一个名为 activities.txt 的文件,其中包含 Skiing
的内容。
3. 使用 open 方法
我们要学习的最后一种创建和写入文件的方法是 open
。你可以使用 w
(”写”)标记打开文件:
const { open} = require('fs/promises'); async function openFile(fileName, data) { try { const file = await open(fileName, 'w'); await file.write(data); console.log(`Opened file ${fileName}`); } catch (error) { console.error(`Got an error trying to open the file: {error.message}`); } }
现在调用 openFile
函数:
openFile('tasks.txt', 'Do homework');
使用 node 命令运行脚本后,将创建一个包含初始内容的名为 tasks.txt 的文件:
Do homework
如何读取文件
既然我们已经知道了如何创建和写入文件,那就来学习读取文件内容吧。为此,我们将使用文件系统模块中的 readFile
方法。
用以下代码创建一个名为 readThisFile.js 的文件:
// readThisFile.js const { readFile } = require('fs/promises'); async function readThisFile(filePath) { try { const data = await readFile(filePath); console.log(data.toString()); } catch (error) { console.error(`Got an error trying to read the file: {error.message}`); } }
现在,让我们通过调用 readThisFile
函数来读取我们创建的所有三个文件:
readThisFile('activities.txt'); readThisFile('friends.txt'); readThisFile('tasks.txt');
最后,使用以下 node 命令执行脚本:
node readThisFile.js
你应该在控制台中看到以下输出:
Skiing Do homework Bob
这里有一点需要注意:readFile
方法以异步方式读取文件。这意味着读取文件的顺序和在控制台中打印响应的顺序可能不一致。你必须使用同步版本的 readFile
方法才能按顺序读取文件。稍后我们将在这里看到这一点。
如何重命名文件
要重命名文件,请使用 fs 模块中的 rename 方法。让我们创建一个名为 rename-me.txt 的文件。我们将以编程方式重命名该文件。
用以下代码创建一个名为 renameFile.js 的文件:
const { rename } = require('fs/promises'); async function renameFile(from, to) { try { await rename(from, to); console.log(`Renamed ${from} to ${to}`); } catch (error) { console.error(`Got an error trying to rename the file: ${error.message}`); } }
你可能已经注意到,重命名方法需要两个参数。一个是源文件名,另一个是目标文件名。
现在让我们调用上述函数来重命名文件:
const oldName = "rename-me.txt"; const newName = "renamed.txt"; renameFile(oldName, newName);
像以前一样,使用 node 命令执行脚本文件,重命名文件:
node renameFile.js
如何移动文件
将文件从一个目录移动到另一个目录与重命名文件路径类似。因此,我们可以使用 rename
方法本身来移动文件。
让我们创建两个文件夹:from 和 to。然后在 from 文件夹中创建一个名为 move-me.txt 的文件。
接下来,我们将编写代码来移动 move-me.txt 文件。用以下代码段创建一个名为 moveFile.js 的文件:
const { rename } = require('fs/promises'); const { join } = require('path'); async function moveFile(from, to) { try { await rename(from, to); console.log(`Moved ${from} to ${to}`); } catch (error) { console.error(`Got an error trying to move the file: ${error.message}`); } }
正如你所看到的,我们像以前一样使用 rename
方法。但为什么我们需要从 path
模块(是的,path 是 Node.js 的另一个重要模块)导入 join
方法呢?
join
方法用于将多个指定的路径段连接成一条路径。我们将用它来形成源文件和目标文件名的路径:
const fromPath = join(__dirname, "from", "move-me.txt"); const destPath = join(__dirname, "to", "move-me.txt"); moveFile(fromPath, destPath);
就是这样!如果你执行 moveFile.js 脚本,就会看到 move-me.txt 文件被移动到 to 文件夹。
如何复制文件
我们使用 fs
模块中的 copyFile
方法将文件从源文件复制到目标文件。
请看下面的代码片段:
const { copyFile } = require('fs/promises'); const { join } = require('path'); async function copyAFile(from, to) { try { await copyFile(from, to); console.log(`Copied ${from} to ${to}`); } catch (err) { console.error(`Got an error trying to copy the file: ${err.message}`); } }
现在,您可以使用以下命令调用上述函数:
copyAFile('friends.txt', 'friends-copy.txt');
它会将 friends.txt
的内容复制到 friends-copy.txt 文件中。
这很好,但如何复制多个文件呢?
您可以使用 Promise.all
API 来并行执行多个承诺:
async function copyAll(fromDir, toDir, filePaths) { return Promise.all(filePaths.map(filePath => { return copyAFile(join(fromDir, filePath), join(toDir, filePath)); })); }
现在,您可以提供所有文件路径,以便从一个目录复制到另一个目录:
copyFiles('from', 'to', ['copyA.txt', 'copyB.txt']);
您还可以使用这种方法来执行其他操作,如并行移动、写入和读取文件。
如何删除文件
我们使用 unlink
链接的方法来删除文件:
const { unlink } = require('fs/promises'); async function deleteFile(filePath) { try { await unlink(filePath); console.log(`Deleted ${filePath}`); } catch (error) { console.error(`Got an error trying to delete the file: ${error.message}`); } }
请记住,要删除文件,您需要提供文件的路径:
deleteFile('delete-me.txt');
请注意,如果路径是指向另一个文件的符号链接,unlink 方法将取消符号链接,但原始文件将保持不变。稍后我们将进一步讨论符号链接。
如何更改文件权限和所有权
在某些情况下,您可能希望以编程方式更改文件权限。这对于使文件成为只读文件或完全可访问文件非常有用。
我们将使用 chmod
方法来更改文件权限:
const { chmod } = require('fs/promises'); async function changePermission(filePath, permission) { try { await chmod(filePath, permission); console.log(`Changed permission to ${permission} for ${filePath}`); } catch (error) { console.error(`Got an error trying to change permission: ${error.message}`); } }
我们可以通过文件路径和权限位掩码来更改权限。
下面是将文件权限改为只读的函数调用:
changePermission('permission.txt', 0o400);
与权限类似,你也可以通过编程改变文件的所有权。我们使用 chown
方法来实现这一功能:
const { chown } = require('fs/promises'); async function changeOwnership(filePath, userId, groupId) { try { await chown(filePath, userId, groupId); console.log(`Changed ownership to ${userId}:${groupId} for ${filePath}`); } catch (error) { console.error(`Got an error trying to change ownership: ${error.message}`); } }
然后,我们使用文件路径、用户 ID 和组 ID 调用该函数:
changeOwnership('ownership.txt', 1000, 1010);
如何创建符号链接
符号链接(也称 symlink)是一个文件系统概念,用于创建指向文件或文件夹的链接。我们创建符号链接是为了在文件系统中创建指向目标文件/文件夹的快捷方式。Node.js filesystem
模块提供了创建符号链接的 symlink
方法。
要创建符号链接,我们需要传递目标文件路径、实际文件路径和类型:
const { symlink } = require('fs/promises'); const { join } = require('path'); async function createSymlink(target, path, type) { try { await symlink(target, path, type); console.log(`Created symlink to ${target} at ${path}`); } catch (error) { console.error(`Got an error trying to create the symlink: ${error.message}`); } }
我们可以用:
createSymlink('join(__dirname, from, symMe.txt)', 'symToFile', 'file');
在这里,我们创建了一个名为 symToFile
的符号链接。
如何查看文件的更改
你知道可以查看文件的更改吗?这是一种监控更改和事件的好方法,尤其是在你意想不到的时候。你可以捕捉和审核这些变化,以便日后查看。
watch
方法是观察文件变化的最佳方法。还有一种名为 watchFile
的替代方法,但其性能不如 watch
方法。
到目前为止,我们已经使用了带有 async/await 关键字的文件系统模块方法。让我们通过这个例子看看 callback
函数的用法。
watch
方法接受文件路径和回调函数作为参数。每当文件发生活动时,回调函数就会被调用。
我们可以利用 event
参数获取有关活动的更多信息:
const fs = require('fs'); function watchAFile(file) { fs.watch(file, (event, filename) => { console.log(`${filename} file Changed`); }); }
通过传递文件名给 watch
来调用函数:
watchAFile('friends.txt');
现在,我们将自动捕获 friends.txt
文件中的任何活动。
在 Node.js 中处理目录(文件夹)
现在我们来学习如何对目录或文件夹执行操作。重命名、移动和复制等许多操作与我们在文件中看到的类似。不过,一些特定的方法和操作只能在目录上使用。
如何创建目录
我们使用 mkdir
方法创建目录。需要将目录名称作为参数传递:
const { mkdir } = require('fs/promises'); async function createDirectory(path) { try { await mkdir(path); console.log(`Created directory ${path}`); } catch (error) { console.error(`Got an error trying to create the directory: ${error.message}`); } }
现在,我们可以使用目录路径调用 createDirectory
函数:
createDirectory('new-directory');
这将创建一个名为 new-directory 的目录。
如何创建临时目录
临时目录不是常规目录。它们对操作系统有特殊意义。你可以使用 mkdtemp()
方法创建临时目录。
让我们在操作系统的临时目录中创建一个临时文件夹。我们从 os
模块的 tmpdir()
方法中获取临时目录位置的信息:
const { mkdtemp } = require('fs/promises'); const { join } = require('path'); const { tmpdir } = require('os'); async function createTemporaryDirectory(fileName) { try { const tempDirectory = await mkdtemp(join(tmpdir(), fileName)); console.log(`Created temporary directory ${tempDirectory}`); } catch (error) { console.error(`Got an error trying to create the temporary directory: ${error.message}`); } }
现在,让我们调用带有目录名的函数来创建目录:
createTemporaryDirectory('node-temp-file-');
请注意,Node.js 会在创建的临时文件夹名称末尾添加六个随机字符,以保持其唯一性。
如何删除目录
您需要使用 rmdir()
方法来删除目录:
const { rmdir } = require('fs/promises'); async function deleteDirectory(path) { try { await rmdir(path); console.log(`Deleted directory ${path}`); } catch (error) { console.error(`Got an error trying to delete the directory: ${error.message}`); } }
接下来,通过传递要删除的文件夹的路径来调用上述函数:
deleteDirectory('new-directory-renamed');
同步与异步 API
到目前为止,我们已经看到了很多文件系统方法的示例,所有这些方法都是异步使用的。不过,您可能需要同步处理某些操作。
同步操作的一个例子就是一个接一个地读取多个文件。 fs
模块有一个名为 readFileSync()
的方法来完成这一操作:
const { readFileSync } = require('fs'); function readFileSynchronously(path) { try { const data = readFileSync(path); console.log(data.toString()); } catch (error) { console.error(error); } }
请注意,”fs/promises” 软件包中不需要 readFileSync()
方法。这是因为该方法不是异步的。因此,调用上述函数时可以使用
readFileSynchronously('activities.txt'); readFileSynchronously('friends.txt'); readFileSynchronously('tasks.txt');
在这种情况下,将按照调用函数的顺序读取上述所有文件。
Node.js 文件系统模块为其他操作(如读取操作)提供了同步方法。请根据需要明智使用。异步方法对并行执行更有帮助。
处理错误
任何程序员都知道,在执行文件或目录操作时,必须预料到错误并做好处理错误的准备。如果找不到文件,或者没有写入文件的权限,该怎么办?在很多情况下,您都可能会遇到错误。
您应该始终在方法调用周围使用 try-catch 块。这样,如果发生错误,控制将转到 catch 块,你可以在那里查看并处理错误。你可能已经注意到,在上面的所有示例中,我们都使用了 try-catch 块来处理遇到的错误。
小结
让我们回顾一下本教程中涉及的要点:
- Node.js 文件系统 (fs) 模块有许多方法可帮助完成许多底层任务。
- 您可以执行各种文件操作,如创建、写入、重命名、复制、移动、删除等。
- 还可以执行创建、临时目录、移动等多种目录操作。
- 所有方法都可以使用 JavaScript 承诺或回调函数异步调用。
- 如果需要,也可以同步调用这些方法。
- 与同步方法相比,异步方法更受青睐。
- 每次与方法交互时,使用 try-catch 块处理错误。
现在,我们已经熟悉了 Node.js 文件系统,你应该对它的来龙去脉有所了解。如果你想进一步加强你的专业知识,你可能想了解一下 Node.js 流,这也是学习 Node.js 模块的自然进展。流是处理信息交换的有效方法,包括网络调用、文件读/写等。
您可以在 GitHub 代码库中找到本文使用的所有源代码。
您是否计划在下一个项目中使用 Node.js?请在下面的评论区告诉我们您选择 Node.js 的原因。
评论留言