在应用中的许多情况下,您会希望使用文件系统。Node.js 通过为操作系统上的标准文件 I/O 操作创建一个包装器,使这一点变得简单明了。在 Node.js 中,这些功能以 Node.js 本地模块之一 fs 为中心。本章将举例说明如何在 Node.js 应用中使用文件系统模块。在本章中,您将学习如何执行这些操作:
- 检索目录结构
- 导航目录
- 操纵目录结构
- 监视目录的修改
- 读写文件
- 移动和链接文件
- 更改文件权限
- 监视文件的修改
注意文件系统模块包含许多方法,它们不仅是异步的,而且有一个同步的对应物。这些同步方法包含在本章的许多解决方案中,以演示如何使用它们。然而,应该注意的是,除非绝对必要,否则应该避免使用同步版本,因为使用它们通常不是最佳做法。这是因为同步版本将阻塞整个过程,直到它们完成,这可能会对您的应用造成各种形式的破坏。
3-1.正在检索目录结构
问题
您希望从 Node.js 应用中访问一个目录或一组目录的结构。
解决办法
为了掌握用于检索目录结构的 Node.js 实用程序,您必须首先通过在代码中使用require('fs') 来获得文件系统模块。然后,您希望获得一些关于目标目录的信息。让我们假设您想要打印 Node.js 应用中与当前目录相关的所有信息。首先,您可以定位当前目录,即执行 Node.js 脚本的目录,如清单 3-1 所示。
清单 3-1 。指向 Node.js 的当前目录
var fs = require('fs');
var out;
console.log(__dirname);
//read current directory asynchronously
fs.realpath(__dirname, function(err, /* [cache], */ path) {
if (err) {
console.log(err);
return;
}
console.log('realpath async: ' + path);
});
out = fs.realpathSync(__dirname);
console.log('real path sync: ' + out);
fs.stat(__dirname, function(err, stat) {
if (err) return;
var isDir = false;
fs.readdir(__dirname, function(err, contents) {
if (err) return;
contents.forEach(function(f) {
console.log('contents: ' + f);
});
});
});
//get list of what’s in the directory
out = fs.readdirSync(__dirname);
console.log('readdir sync: ' + out);该解决方案会带来什么结果?它基于当前工作目录生成一个列表,列出该目录中包含的内容。该列表类似于清单 3-2 中的内容。
清单 3-2 。清单 3-1 的输出
$ node 3-1-1.js
/home/cgack/Dropbox/book/code/Ch03
real path sync: /home/cgack/Dropbox/book/code/Ch03
readdir sync: 3-1-1.js,3-1-2.js
contents: 3-1-1.js
contents: 3-1-2.js
realpath async: /home/cgack/Dropbox/book/code/Ch03虽然该解决方案对于运行代码以获取应用实例化位置的目录结构是有效的,但是它使得解析任意目录的结构以及与调用 Node.js 脚本的位置相关的目录变得困难。这可以通过稍微重构清单 3-1 来解决,以允许如清单 3-3 所示的命令行参数。
清单 3-3 。重构目录摘要
var fs = require('fs');
var out;
var args;
//Normalize the arguments
args = process.argv.splice(2);
args.forEach(function(arg) {
console.log(arg);
//read current directory asynchronous
fs.realpath(arg, function(err, /* [cache], */ path) {
if (err) {
console.log(err);
return;
}
console.log('realpath async: ' + path);
});
out = fs.realpathSync(arg);
console.log('real path sync: ' + out);
fs.stat(arg, function(err, stat) {
if (err) return;
fs.readdir(arg, function(err, contents) {
if (err) return;
contents.forEach(function(f) {
console.log('contents: ' + f);
});
});
});
//get list of what’s in the directory
out = fs.readdirSync(arg);
console.log('readdir sync: ' + out);
});你可以看到这个解决方案提供了更多。它接受一个参数列表,对它们进行规范化,然后遍历提供的目录,产生一个输出。这意味着您可以向 Node.js 应用传递两个相对路径,它将循环并产生类似于清单 3-4 中输出的结果。
清单 3-4 。多路输出
$ node 3-1-2.js ...
.
real path sync: /home/cgack/Dropbox/book/code/Ch03
readdir sync: 3-1-1.js,3-1-2.js
..
real path sync: /home/cgack/Dropbox/book/code
readdir sync: 2-6-2.js,Ch01,Ch02,Ch03
contents: 3-1-1.js
contents: 3-1-2.js
contents: 2-6-2.js
contents: Ch01
contents: Ch02
contents: Ch03
realpath async: /home/cgack/Dropbox/book/code它是如何工作的
现在你检查所有这些是如何工作的。您会看到,一般来说,调用静态的硬编码目录和允许命令行参数传递到您的命令之间的区别是灵活性的额外好处。让我们从读取目录信息的代码开始,然后您可以检查实现中的差异。
Node.js 文件系统模块围绕标准 POSIX 命令提供了大量有用的包装器,这些命令几乎无处不在(一些操作系统在实现上有所不同)。本节使用的命令有readdir、stat和realpath。
readdir的 Node.js 实现是一个读取目录的简单命令。然而,您会注意到,在解决方案中有两个对readdir的单独调用。一个是到readdir () ,一个是到readdirSync () 。readdirSync是文件系统目录读取的同步实现,如清单 3-5 所示。
清单 3-5 。readdirSync
fs.readdirSync = function(path) {
nullCheck(path);
return binding.readdir(pathModule._makeLong(path));
};这只是检查路径是否存在,然后返回该路径。这个调用的另一个版本是异步的(清单 3-6 ),并且如您所料,接受回调。回调接受两个参数:一个错误参数和另一个保存路径信息的参数。
清单 3-6 。readdir ()
fs.readdir = function(path, callback) {
callback = makeCallback(callback);
if (!nullCheck(path, callback)) return;
binding.readdir(pathModule._makeLong(path), callback);
};与readdir类似,名为realpath的 Node.js 函数有同步和异步两种形式。realpath函数返回给定路径的绝对路径名。因此,这实际上是收集当前目录信息的两种方式。函数realpath检索绝对路径,而readdir检索关于其内容的信息。Readdir只能检索一个目录中的文件或目录列表,所以为了找到一个目录的更多细节,你需要一些不同的东西。这个不同的方法就是stat () 。stat()
那么,所提供的示例的两个不同版本呢?一个是基于静态路径,实际上是一个名为 __ dirname 的 Node.js 全局变量。_dirname变量是相对于每个模块的,它表示当前正在执行的 Node.js 脚本的路径。所以当你在清单 3-1 中使用它时,你是在告诉 Node.js 和你调用的文件系统模块,利用 Node.js 模块的路径作为每个文件系统调用的路径参数。
这是相当有限的,所以你可以看到在的清单 3-3 中,模块被打开以利用一组传递给模块 Node.js 的命令行参数。这是利用了包含argv元素中参数列表的全局流程对象。在清单中,您会看到这些参数被规范化以删除前两个参数—'node <file>'—and then parse,剩下的参数作为一个数组。然后,这个数组被用作每个 Node.js 文件系统方法的路径参数,为目录信息检索代码的初始实现提供了更多的功能。
3-2.浏览目录
问题
在许多使用文件系统的应用中,您可能希望以某种形式遍历目录结构。
解决办法
使用 Node.js 应用遍历机器的目录结构是通过使用fs模块完成的。这个解决方案从第 3-1 节的解决方案停止的地方开始,因为它从读取一个目录开始,然后它将相应地在整个目录中移动。目录结构的解析是递归的,并产生一个包含文件和目录的数组。这个 Node.js 应用如清单 3-7 所示。
清单 3-7 。遍历目录
var fs = require('fs');
var out;
var args;
/**
* To parse directory structure given a starting point - recursive
*/
function traverseDirectory(startDir, usePath, callback) {
if (arguments.length === 2 && typeof arguments[1] === 'function') {
callback = usePath;
usePath = false;
}
//Hold onto the array of items
var parsedDirectory = [];
//start reading a list of whats contained
fs.readdir(startDir, function(err, dirList) {
if (usePath) {
startDir = fs.realpathSync(startDir);
}
if (err) {
return callback(err);
}
//keep track of how deep we need to go before callback
var listlength = dirList.length;
if (!listlength) {
return callback(null, parsedDirectory);
}
//loop through the directory list
dirList.forEach(function(file) {
file = startDir + '/' + file;
fs.stat(file, function(err, stat) {
//note the directory or file
parsedDirectory.push(file);
//recursive if this is a directory
if (stat && stat.isDirectory()) {
//recurse
traverseDirectory(file, function(err, parsed) {
// read this directory into our output
parsedDirectory = parsedDirectory.concat(parsed);
//check to see if we've exhausted our search
if (!--listlength) {
callback(null, parsedDirectory);
}
});
} else {
//check to see if we've exhausted the search
if (!--listlength) {
callback(null, parsedDirectory);
}
}
});
});
});
}
//Normalize the arguments
args = process.argv.splice(2);
//loop through the directories
args.forEach(function(arg) {
// use provided path
traverseDirectory(arg, function(err, result) {
if (err) {
console.log(err);
}
console.log(result);
});
//use full path
traverseDirectory(arg, true, function(err, result) {
if (err) {
console.log(err);
}
console.log(result);
});
});这个遍历产生了一个控制台输出,类似于清单 3-8 中的所示。
清单 3-8 。遍历的输出
gack∼/Dropbox/book/code/Ch03: node 3-2-1.js.
[ './3-1-1.js',
'./3-1-2.js',
'./3-2',
'./3-2-1.js',
'./3-2/file.txt',
'./3-2/sub directory',
'./3-2/sub directory/file.txt' ]
[ '/Users/gack/Dropbox/book/code/Ch03/3-1-1.js',
'/Users/gack/Dropbox/book/code/Ch03/3-1-2.js',
'/Users/gack/Dropbox/book/code/Ch03/3-2',
'/Users/gack/Dropbox/book/code/Ch03/3-2-1.js',
'/Users/gack/Dropbox/book/code/Ch03/3-2/file.txt',
'/Users/gack/Dropbox/book/code/Ch03/3-2/sub directory',
'/Users/gack/Dropbox/book/code/Ch03/3-2/sub directory/file.txt' ]它是如何工作的
这个解决方案首先从第 3-1 节吸取教训,并通过命令行提供参数,然后这些参数被规范化。这指导应用在开始遍历目录结构时使用哪些路径,这在函数traverseDirectory中处理。
traverseDirectory函数接受一个路径(或起始目录)、一个将起始路径转换为完整路径的可选标志和一个回调函数。可选的usePath标志是通过检查是否只有两个参数被传入以及第二个参数是否是一个函数来确定的,这表明提供了回调。
if (arguments.length === 2 && typeof arguments[1] === 'function') {
callback = usePath;
usePath = false;
}usePath标志是一个选项,如果被设置,它将使用fs.realpath函数解析提供给traverseDirectory方法的目录。因此这将转换作为“.”提供的路径,表示当前工作目录,到应用的实际路径(即“/home/username/apps/”)。
对目录结构的实际遍历是从调用fs.readdir开始的,正如你在 3-1 节中看到的,它提供了一个回调函数,给出了驻留在目录中的内容列表。然后对这个返回的列表进行检查,以确保目录中有可供解析的信息。如果不存在任何结果,函数将使用提供的回调函数退出。或者,如果目录列表中有结果,则存储该数组的长度(listlength)以跟踪目录树中要解析的剩余项。
然后循环遍历目录列表数组,将函数fs.stat应用于它包含的每一项。fs.stat函数 返回一个fs.stat对象,详细描述文件系统中某个文件的信息。然后,traverseDirectory函数将调用fs.stat对象的文件(或目录)存储到输出数组parsedDirectory.中,然后通过stat.isDirectory函数检查 stat 对象,看结果是否是一个目录。如果结果为真,那么调用traverseDirectory函数,传入目录—递归解析目录。如果 stat 不是一个目录,该函数假定它是一个文件,但是,无论它是否是一个文件,目录列表长度变量都将递减,并检查是否还有任何剩余条目,if (!—listlength)。在没有任何剩余条目的情况下,函数通过parsedDirectory数组返回回调。结果被传递给回调函数,在本例中,回调函数将结果记录到控制台。
3-3.操纵目录结构
问题
您希望通过 Node.js 应用添加和删除目录来操作目录的结构。
解决办法
该解决方案有两种形式,将分两部分进行描述。第一部分是删除目录。在 Node.js 中,这就像调用文件系统模块的 make directory 或 make directory 同步函数一样简单,fs.mkdir 和 fs.mkdirSync 。这两个函数都显示在一个例子中,清单 3-9 。
清单 3-9 。创建目录同步和异步功能
var fs = require('fs'),
dirExists = false;
//Normalize the arguments
args = process.argv.splice(2);
//loop through named args
args.forEach(function(arg) {
//mkdirSync - manually handle errors
try {
fs.mkdirSync(arg);
} catch(err) {
handleError(err);
}
//mkdir async
fs.mkdir(arg, function(err) {
if (err) handleError(err);
});
*/
});
function handleError(err) {
console.log(err);
if (err.code === 'EEXIST') {
console.log('That directory already exists');
} else {
console.log('An error occurred creating the directory');
}
}处理目录结构的解决方案的第二部分涉及删除现有目录的 Node.js 方法。删除代码(如清单 3-10 所示)本质上是创建的反向操作,在异常处理上略有不同。
清单 3-10 。删除目录
var fs = require('fs'),
dirExists = false;
//Normalize the arguments
args = process.argv.splice(2);
//loop through named args
args.forEach(function(arg) {
//rmdir sync
try {
fs.rmdirSync(dir);
} catch(err) {
handleError(err);
}
//rmdir async
fs.rmdir(arg, function(err) {
if (err) handleError(err);
});
});
function handleError(err) {
console.log(err);
if (err.code === 'ENOENT') {
console.log('That directory does not exist');
} else if (err.code === 'ENOTEMPTY') {
console.log('Cannot remove directory because it is not empty');
} else {
console.log('An error occurred removing the directory');
}
}它是如何工作的
使用mkdir函数创建目录。mkdir函数也接受一个只有错误参数的回调函数。当目录已经存在时抛出的错误代码为EEXIST,因此在本例中,这是在handleError函数中显式处理的。同步版本的mkdir(即mkdirSync)不提供错误回调,所以在例子中你可以看到它是在 try-catch 内部创建的,其中 catch 提供了与异步回调相同的错误处理程序。同步和异步函数accept可选的第二个参数,指定创建目录的模式,默认为0777。如果您想要限制权限,您可以将创建模式更改为任何内容(例如,0755)),限制除用户之外的所有人对目录的读取和执行权限。
目录的删除类似于创建。移除异步函数rmdir接受带有错误参数的回调。这个回调处理的常见错误是EONENT和ENOTEMPTYT6。EONENT 当目录不存在,试图从目录结构中删除时抛出。ENOTEMPTY 当你试图删除一个非空的目录时抛出。这些都是通过handleError功能处理的。在本章的后面,你将看到如何移动和重命名文件,如果你想删除一个非空的文件夹,这将是必要的。
3-4.查看修改目录
问题
您希望在运行 Node.js 应用的过程中观察目录结构的变化。
解决办法
监视目录结构的解决方案遵循 3-2 节中的解决方案,遍历目录将内容读入数组。对于这个解决方案,您可以做两件事。一种方法是再次遍历目录结构,缓存目录和子目录的初始状态。然后,您将设置再次检查目录结构并比较这两个数组的输出的时间间隔。这不是最佳解决方案,因为 Node.js 在文件系统模块中有一个内置的实用程序,它将创建一个名为fs.watch 的文件系统观察器对象。清单 3-11 中显示了实现的方式。
`清单 3-11 。观察变化
/**
* Watching a directory
*/
var os = require('os'),
fs = require('fs'),
out,
args;
/**
* To parse directory structure given a starting point - recursive
*/
function traverseDirectory(startDir, usePath, callback) {
if (arguments.length === 2 && typeof arguments[1] === 'function') {
callback = usePath;
usePath = false;
}
//Hold onto the array of items
var parsedDirectory = [];
//start reading a list of what’s contained
fs.readdir(startDir, function(err, dirList) {
if (usePath) {
startDir = fs.realpathSync(startDir);
}
if (err) {
return callback(err);
}
//keep track of how deep we need to go before callback
var listlength = dirList.length;
if (!listlength) {
return callback(null, parsedDirectory);
}
//loop through the directory list
dirList.forEach(function(file) {
//WIndows is special
file = startDir + (os.platform() === 'win32' ? '\\' : '/') + file;
fs.stat(file, function(err, stat) {
//note the directory or file
parsedDirectory.push(file);
//recursive if this is a directory
if (stat && stat.isDirectory()) {
//recurse
traverseDirectory(file, function(err, parsed) {
// read this directory into our output
parsedDirectory = parsedDirectory.concat(parsed);
//check to see if we've exhausted our search
if (!--listlength) {
callback(null, parsedDirectory);
}
});
} else {
//check if we've exhausted the search
if (!--listlength) {
callback(null, parsedDirectory);
}
}
});
});
});
}
//Normalize the arguments
args = process.argv.splice(2);
//loop through the directories
args.forEach(function(arg) {
traverseDirectory(arg, true, function(err, result) {
result.forEach(function(i) {
fs.watch(i, filesystemListener);
});
});
});
function filesystemListener(e, f) {
console.log(f + ': ' + e);
}这个解决方案非常强大,因为它还会检查单个文件的更改,只需调用一个函数。然而,正如您将看到的,fs.watch方法是不稳定的,可能无法在 Node.js 环境中按预期执行。正因为如此,监视目录结构变化的另一种方法如清单 3-12 所示。
清单 3-12 。检查目录结构变化
function checkSame(err, result) {
if (err) {
console.log(err);
}
if (initialDir.length === 0) {
initialDir = result;
} else {
secondaryDir = result;
//let’s compare these
if (secondaryDir.length !== initialDir.length) {
console.log('directory structure changed');
clearInterval(checkInt);
}
secondaryDir.sort();
initialDir.sort();
for (var i=0, ii = secondaryDir.length; i < ii; i++) {
if (secondaryDir[i] !== initialDir[i]) {
if (secondaryDir.indexOf(initialDir[i]) < 0) {
console.log(initialDir[i] + ' removed');
}
if (initialDir.indexOf(secondaryDir[i]) < 0) {
console.log(secondaryDir[i] + ' added');
}
clearInterval(checkInt);
}
}
}
}
var checkInt;
//Normalize the arguments
args = process.argv.splice(2);
//loop through the directories
args.forEach(function(arg) {
checkInt = setInterval(traverseDirectory, 2e3, arg, true, checkSame);
});它是如何工作的
清单 3-11 中的例子展示了目录树的遍历,并添加了一个非常重要的函数。函数产生一个文件名和目录名的数组。然后循环这些结果,调用fs。watch作用于各条路径。fs.watch 函数是文件系统监视功能的一部分,这将在后面的 3-10 节中介绍。
请注意,fs.watch功能并不总是跨平台可用,并且(从 Node.js 版本 0.10.5 开始)仍然被认为是“不稳定的”因此,如清单 3-13 所示的替代实现在跨平台方面更加可靠,并且只寻找文件系统结构的变化。同样,该系统利用了traverseDirectory功能,但是间隔一段时间。这个时间间隔将每两秒钟解析一次目录结构,但是如果解析一个大的目录树需要比递归解析更长的时间,您可能需要调整这个时间间隔。在第一次迭代之后,在checkSame函数中,将原始解析的数组与当前解析的数组进行比较。如果检测到更改,则会记录下来。如果数组的长度不同,则首先检测到更改,这意味着底层结构已被修改(即文件删除)。然后对数组进行排序,然后检查每一项,看它是否还存在于另一个结果集中。
清单 3-13 。检查目录差异
if (secondaryDir.length !== initialDir.length) {
console.log('directory structure changed');
clearInterval(checkInt);
}
secondaryDir.sort();
initialDir.sort();
for (var i=0, ii = secondaryDir.length; i < ii; i++) {
if (secondaryDir[i] !== initialDir[i]) {
if (secondaryDir.indexOf(initialDir[i]) < 0) {
console.log(initialDir[i] + ' removed');
}
if (initialDir.indexOf(secondaryDir[i]) < 0) {
console.log(secondaryDir[i] + ' added');
}
clearInterval(checkInt);
}
}在监视目录变化时使用这种方法将产生类似于清单 3-14 中输出的结果,当目录名从"that"更改为"this"时产生:
清单 3-14 。注意目录结构的变化
$ node 3-4-1.js.
/home/cgack/book/code/Ch03/3-4/now/that removed
/home/cgack/book/code/Ch03/3-4/now/this added3-5.读取文件
问题
在构建 Node.js 应用的过程中,您需要从文件系统中访问和读取一个文件。
解决办法
当使用文件系统模块时,从文件系统中读取文件相当简单。文件系统模块提供了多种读取文件的方法。在清单 3-15 中,解决方案显示了使用文件系统在 Node.js 中读取文件的三种主要方法:readFile 、readFileSync 和createReadStream 。
`清单 3-15 。读取文件
/**
* Reading a file
*/
var fs = require('fs'),
args;
args = process.argv.splice(2);
args.forEach(function(arg){
//async read
fs.readFile(arg, 'utf8', function(err, data) {
if (err) console.log(err);
console.log(data);
});
//synchronicity
var file = fs.readFileSync(arg, 'utf8');
console.log(file);
//with a readable stream
var readstrm = fs.createReadStream(arg, {flag: 'r', encoding: 'utf8'});
readstrm.on('data', function(d) {
console.log(d);
});
});它是如何工作的
读取 Node.js 中的文件可以采取不同的形式。首先,您可以利用标准的异步函数fs.readFile。该函数将接受一个文件名(这是必需的)、一个可选的选项参数和一个回调(也是必需的)。options 参数用于设置encoding (,它将在读取文件时设置文件缓冲区的编码,添加到 options 对象的是flag,它设置打开文件时使用的标志:它将始终是‘r’。
位于其核心的readFile函数调用函数fs.open,该函数将打开文件。标志选项总是设置为“r ”,意味着文件将被打开以供读取。在readFile的情况下,清单 3-16 中的所示的打开方法将获取文件的大小,然后创建一个与该大小相匹配的缓冲区。然后在read()功能中读取该缓冲区。
清单 3-16 。打开并读取 fs.readFile 中的()
fs.open(path, flag, 438 /*=0666*/, function(er, fd_) {
if (er) return callback(er);
fd = fd_;
fs.fstat(fd, function(er, st) {
if (er) return callback(er);
size = st.size;
if (size === 0) {
// the kernel lies about many files.
// Go ahead and try to read some bytes.
buffers = [];
return read();
}
buffer = new Buffer(size);
read();
});
});
function read() {
if (size === 0) {
buffer = new Buffer(8192);
fs.read(fd, buffer, 0, 8192, -1, afterRead);
} else {
fs.read(fd, buffer, pos, size - pos, -1, afterRead);
}
}你可以看到fs.readFile's内部read()函数调用fs.read,指向文件描述符和被创建为打开文件大小的缓冲区。在执行afterRead函数后,结果最终被发送到fs.readFile的回调函数,文件被关闭。close 方法实际上将数据从缓冲区发送回回调。
清单 3-17 。关闭将 readFile 数据发送回调用者的事件
function close() {
fs.close(fd, function(er) {
if (size === 0) {
// collected the data into the buffers list.
buffer = Buffer.concat(buffers, pos);
} else if (pos < size) {
buffer = buffer.slice(0, pos);
}
if (encoding) buffer = buffer.toString(encoding);
return callback(er, buffer);
});
}正如您所想象的,读取文件的下一个方法fs.readFileSync遵循与fs.readFile函数相似的模式,但是它只是同步操作。这不会导致包含从文件中读取的数据的回调,但同步版本会直接返回数据,并应用适当的编码。
if (encoding) buffer = buffer.toString(encoding);
return buffer;最后,在使用 Node.js 读取文件的解决方案中,您创建了一个可读的流来解析文件。可读流是使用fs.createReadStream函数创建的,正如它的名字所预示的那样:它创建一个ReadStream。一个ReadStream是一个带有open事件的可读流,它返回流用来读取文件的文件描述符(fd)。传递到可读流中的选项是一个具有以下默认值的对象:
{ flags: 'r', encoding: null, fd: null, mode: 0666, bufferSize: 64 * 1024, autoClose: true }有两个额外的选项可以传递:start 和 end。它们指定了您希望读取的文件的特定部分。
使用这些选项设置创建ReadStream,然后打开流。打开流调用fs.open,允许文件被打开和读取,如清单 3-18 所示。
清单 3-18 。ReadStream 调用 fs.open 并读取文件
ReadStream.prototype.open = function() {
var self = this;
fs.open(this.path, this.flags, this.mode, function(er, fd) {
if (er) {
if (this.autoClose) {
self.destroy();
}
self.emit('error', er);
return;
}
self.fd = fd;
self.emit('open', fd);
// start the flow of data.
self.read();
});
};3-6.写文件
问题
您希望利用 Node.js 将内容或数据写入应用中的文件。
解决方案
从 Node.js 编写文件的解决方案类似于第 3-5 节中提到的方法。就像读取文件一样,在 Node.js 中写入文件有几种方法。有典型的异步方法(fs.writeFile ),该函数的同步版本(fs.writeFileSync ),以及写入文件的流版本(createWriteStream ))。还有一种方法,就是把数据追加到一个叫做fs.appendFile 的文件中。这些功能如清单 3-19 所示。
清单 3-19 。写文件
/**
* Writing files
*/
var fs = require('fs');
//initial write
fs.writeFile('write.txt', 'This is the contents!', function(err) {
if (err) throw err;
console.log('huzzah');
});
try {
fs.writeFileSync('./doesnotexist/newfile.txt', 'content');
} catch(err) {
console.log('unable to create a file in a non existent sub directory');
console.log(err);
}
//appending
fs.appendFile('write.txt', 'More content', function(err) {
if (err) throw err;
console.log('appended');
});
var ws = fs.createWriteStream('write.txt');
ws.write('new content\r\n', function() {
console.log('write stream hath written.');
});清单 3-19 展示了如何使用三种不同的方法在 Node.js 中编写一个文件。同步方法有目的地针对不存在的子目录中的文件,以便演示这种情况的错误处理,并观察写入文件不会创建目录。执行该解决方案的输出将类似于清单 3-20 中所示的例子。
清单 3-20 。写文件输出
gack∼/Dropbox/book/code/Ch03: node 3-6-1.js
unable to create a file in a non existent sub directory
{ [Error: ENOENT, no such file or directory './doesnotexist/newfile.txt']
errno: 34,
code: 'ENOENT',
path: './doesnotexist/newfile.txt',
syscall: 'open' }
write stream hath written.
appended
huzzah它是如何工作的
让我们从异步fs.writeFile开始,研究一下如何在 Node.js 中编写文件。fs.writeFile最多接受四个参数:路径、数据、选项和回调。该路径指向您希望写入的文件。该文件不需要存在,因为如果不存在,它将被创建。然而,如果你的目标是一个不存在的目录,writeFile功能不会自动为你创建目录。数据参数是您希望写入文件的数据,可以是字符串或缓冲区的形式。options 对象包含文件访问的编码、模式和标志。就像使用readFile方法一样,编码是唯一可配置的选项,因为模式和标志的设置被设置为mode: 438 /*=0666*/ and flag: 'w'。回调将传递任何错误以便处理它们。
一旦在writeFile功能中设置了默认值,就会调用fs.open。因为这个调用设置了'w'标志,它要么创建文件,要么截断文件。然后数据将作为缓冲区写入文件,如果缓冲区是提供的数据类型,字符串将被转换成缓冲区,如清单 3-21 所示。
清单 3-21 。writeFile —打开并写入数据
var flag = options.flag || 'w';
fs.open(path, options.flag || 'w', options.mode, function(openErr, fd) {
if (openErr) {
if (callback) callback(openErr);
} else {
var buffer = Buffer.isBuffer(data) ? data : new Buffer('' + data,
options.encoding || 'utf8');
var position = /a/.test(flag) ? null : 0;
writeAll(fd, buffer, 0, buffer.length, position, callback);
}
});writeAll函数包装了fs.write函数,并将整个缓冲区写入文件。类似于readFile和readFileSync函数,writeFileSync的运行方式与writeFile相同,除了所有函数都是同步的,抛出过程中遇到的任何错误。这就是为什么示例中的代码是在 try-catch 块中编写的,以便很好地捕捉目录不存在时抛出的错误。
在许多情况下,您可能不希望在向文件中写入数据时创建或截断文件。这就是fs.appendFile函数有用的地方。这个函数是一个写文件的工具,只是把数据附加到文件中,而不是写新的数据。它通过简单地改变fs.writeFile的标志选项,然后调用如清单 3-22 所示的函数来实现。
清单 3-22 。改变标志选项
if (!options.flag)
options = util._extend({ flag: 'a' }, options);
fs.writeFile(path, data, options, callback);如图所示,这将用fs.open打开文件。将标志设置为“a”将打开附加文件,如果文件不存在,允许创建它。
流方法为fs.createWriteStream ,创建可写流。createWriteStream方法将接受一个路径和选项。可以设置的选项有fd(一个文件描述符)、标志、模式和开始。fd 将指向要写入数据的文件句柄。该标志默认为w,,以便打开文件进行写入。模式选项默认为0666,即读写权限。start 选项告诉我们在文件中从哪里开始写数据。应该注意的是,如果您指定的起始位置超过了文件长度的末尾,您将会在文件中得到一堆缓冲区输出,而不是预期的数据或文本。
通过首先打开文件,然后调用内部 _ write函数来编写WriteStream。该函数将确保要写入的数据是正确的,并且文件确实是打开的。一旦确认,文件将使用fs.write方法写入,如清单 3-23 所示。
清单 3-23 。WriteStream 的 _write 方法
WriteStream.prototype._write = function(data, encoding, cb) {
if (!Buffer.isBuffer(data))
return this.emit('error', new Error('Invalid data'));
if (typeof this.fd !== 'number')
return this.once('open', function() {
this._write(data, encoding, cb);
});
var self = this;
fs.write(this.fd, data, 0, data.length, this.pos, function(er, bytes) {
if (er) {
self.destroy();
return cb(er);
}
self.bytesWritten += bytes;
cb();
});
if (this.pos !== undefined)
this.pos += data.length;
};3-7.移动文件
问题
您希望能够从 Node.js 应用移动目录结构中的文件。您很可能会遇到这样的情况:由于某种原因,您需要更改文件的位置。也许您在 Node.js 应用中存储了一个临时文件缓存,您希望将它移动到一个更永久的位置。然后有一个用户表示他想存储他最喜欢的动画。gif 文件保存在更持久的位置。
解决办法
在构建访问文件系统的应用时,移动文件非常重要。在这个问题描述的情况下,你可以使用 Node.js 移动文件,如清单 3-22 所示。
清单 3-24 是用户缓存的一个文件 awesome.gif 的例子。该文件位于文件系统的临时目录3-7/tmp/中,需要移动到保存文件夹3-7/save/中。为了演示这一点,您将看到 Node.js 有多种方法可以实现这一点。其中两个利用了文件系统模块,结合了重命名和renameSync功能来移动文件。
清单 3-24 。移动文件:从命令行开始
/**
* Moving files
*/
var fs = require('fs'),
origPath,
newPath,
args = process.argv;
if (args.length !== 4) {
throw new Error('Invalid Arguments');
} else {
origPath = args[2];
newPath = args[3];
}
// move file asynchronously from tmp to save
fs.rename(origPath, newPath, function(err) {
if (err) throw err;
});您将从一个使用命令行的示例开始,然后您将看到完成在 Node.js 中移动文件的相同任务的另外两种方法。
这是通过提供如下命令行参数来实现的:
$ node 3-7-1.js 3-7/tmp/awesome.gif 3-7/save/awesome.gif除了通过命令行设置之外,您还可以直接在您的应用中实现fs.rename函数。这也可以设置成同步运行,如清单 3-25 所示,或者通过子进程运行,如清单 3-26 所示。
清单 3-25 。同步文件移动
//Synchronous
fs.renameSync(origPath, newPath);清单 3-26 。使用子进程移动文件
// Child process => more in Chapter 5
var child = require('child_process');
child.exec('mv 3-7/tmp/awesome.gif 3-7/save/awesome.gif', function(err, stdout, stderr) {
console.log('out: ' + stdout);
if (stderr) throw stderr;
if (err) throw err;
});它是如何工作的
当您开始研究这是如何工作的时候,首先要看文件系统模块。fs.rename函数执行标准的 POSIX 重命名,定义如下。
rename()函数 将改变一个文件的名称。旧参数指向要重命名的文件的路径名。新参数指向文件的新路径名。
如果旧的或新的参数命名了一个符号链接,rename()将对该符号链接本身进行操作,而不会解析该参数的最后一部分。如果旧参数和新参数解析为同一个现有文件,rename()将成功返回,并且不执行任何其他操作。
这意味着您正在利用操作系统的能力,通过改变文件的路径名来改变文件的位置。当你看到这个解决方案时,你会开始准确地理解它是如何工作的。
首先,您会看到,为了这个示例,使用了传递给 Node.js 进程的参数。您将利用这些参数告诉 Node.js 应用在移动文件时使用哪个路径名。这意味着除了标准的前两个参数node <app.js>,你还需要另外两个参数。
为了验证这些额外的参数,并防止您的 move 函数立即抛出带有错误数量的参数的错误,您需要确保您提供了正确数量的参数。这是通过检查传递的参数数量并在遇到无效数量时抛出一个适当的错误来实现的。
if (args.length !== 4) {
throw new Error('Invalid Arguments');
} else {
origPath = args[2];
newPath = args[3];
}在这个完整性检查之后,如果您有适当数量的参数,那么您可以分配原始路径和新路径变量,它们将被传递到您的fs.rename函数。这个函数接受一个原始路径和一个新路径参数,以及一个回调。回调函数只有在重命名过程因为某种原因失败时才会接受一个错误对象。检查清单 3-27 中fs.rename 的 Node.js 源代码,你会发现该模块只是包装了操作系统的本地重命名功能。
清单 3-27 。fs.rename 源文件
fs.rename = function(oldPath, newPath, callback) {
callback = makeCallback(callback);
if (!nullCheck(oldPath, callback)) return;
if (!nullCheck(newPath, callback)) return;
binding.rename(pathModule._makeLong(oldPath),
pathModule._makeLong(newPath),
callback);
};您可以从源文件中看到,原始路径名和新路径名必须存在,否则nullCheck函数将阻止重命名。您还必须提供文件的现有路径。重命名操作不需要文件本身存在,但是如果你提供了一个不存在的路径,将会抛出一个错误(见清单 3-28 )。
清单 3-28 。路径不存在
$ node 3-7-1.js 3-7/tmp/awesome.gif 3-7/save/does/not/exist/awesome.gif
/Users/gack/Dropbox/book/code/Ch03/3-7-1.js:18
if (err) throw err;
^
Error: ENOENT, rename '3-7/tmp/awesome.gif'解决方案中的下一个例子实现了同步版本的fs.rename、fs.renameSync。这与fs.rename的功能相同,不同之处在于该函数等待直到重命名发生,然后返回(参见清单 3-29 )。
清单 3-29 。同步重命名
fs.renameSync = function(oldPath, newPath) {
nullCheck(oldPath);
nullCheck(newPath);
return binding.rename(pathModule._makeLong(oldPath),
pathModule._makeLong(newPath));
};在移动文件的解决方案中,这两个示例可能是 Node.js 中最常见的方法。您还在解决方案中看到了一种利用标准终端命令来执行文件移动的方法:
'mv 3-7/tmp/awesome.gif 3-7/save/awesome.gif'这是通过利用 Node.js 子流程模块来完成的。利用 Node.js 子流程模块的细节将在第 5 章中进一步讨论。但是,您可以看到,您可以通过这个模块直接执行命令。
3-8.象征性地链接文件
问题
构建 Node.js 应用时,您希望利用符号链接或文件系统中文件的链接。
解决办法
在这个解决方案中,您可以想象您的 Node.js 应用刚刚下载了一个可执行文件,然后您希望通过使用符号链接使该文件在文件系统中可用。为此,有许多方法来建立符号链接。当然,您必须从通过require('fs')导入的文件系统模块开始。然后你将会看到在文件系统中如何链接到文件,以及随后如何读取它们并知道它们链接到哪里有多种版本。
清单 3-30 。象征性地链接文件
/**
* symbolic links
*/
var fs = require('fs');
fs.link('/opt/Sublime Text 2/sublime_text', '/usr/bin/sublime', function(err) {
if (err) throw err;
});
fs.linkSync('/opt/Sublime Text 2/sublime_text', '/usr/bin/sublime');
fs.symlink('/opt/Sublime Text 2/sublime_text', '/usr/bin/sublime', function(err) {
if (err) throw err;
});
fs.symlinkSync('/opt/Sublime Text 2/sublime_text', '/usr/bin/sublime');
fs.readlink('/usr/bin/sublime', function(err, string) {
if (err) throw err;
console.log(string);
});
var rls = fs.readlinkSync('/usr/bin/sublime');
console.log(rls);创建链接有四个函数,有两种不同的类型。首先是fs.link和fs.linkSync。另外两个是fs.symlink和fs.symlinkSync。这些是通过fs.readlink和fs.readlinkSync读取链接的方法的补充。
它是如何工作的
象征性地链接文件和文件系统中的链接文件的行为就像 Node.js 中的文件系统模块中的许多其他项目一样。也就是说,这些函数是标准操作系统命令的包装器。
首先让我们检查一下函数fs.link。这个函数本身不是一个创建符号链接的函数;相反,它是一个包装 POSIX link 命令的函数。该命令将创建一个到现有文件的链接,或通常所说的硬链接。链接函数采用三个参数:原始路径、新路径和一个回调函数,如果出现错误,回调函数将接受错误。这个函数和文件系统模块中的其他函数一样,有一个同步相关函数fs.linkSync。linkSync除了大声说出来很有趣之外,还执行和fs.link一样的硬链接操作;只是它返回结果而不是使用回调。
象征性链接文件的操作方式与fs.link类似。链接文件的功能是fs.symlink。符号链接是一种软链接,与硬链接相对。符号链接表示到另一个文件或目录的链接,就像硬链接一样,有两个明显的区别。首先,符号链接跨卷有效,而不只是作为硬链接指向本地卷。第二,符号链接可以指向任意路径,其中硬链接必须链接到文件系统上的现有文件。
除了操作系统上符号链接和硬链接实现的不同,Node.js 实现非常相似。函数fs.symlink接受三个参数:原始路径、新路径和接受任何发生的错误的回调。这与函数fs.link的签名相同。正如fs.link有一个同步副本一样,fs.symlink也有fs.symlinkSync。同步版本直接返回结果,而不是利用回调。
在文件系统上创建符号链接之前,fs.symlink和fs.symlinkSync函数确实执行了一次检查。该检查是预处理功能;清单 3-31 显示了系统如何确保在 Windows 环境下创建的符号链接使用正确的协议来解析文件路径。
清单 3-31 。符号链接预处理
function preprocessSymlinkDestination(path, type) {
if (!isWindows) {
// No preprocessing is needed on Unix.
return path;
} else if (type === 'junction') {
// Junctions paths need to be absolute and \\?\-prefixed.
return pathModule._makeLong(path);
} else {
// Windows symlinks don't tolerate forward slashes.
return ('' + path).replace(/\//g, '\\');
}
}一旦在文件系统中创建了符号链接或硬链接,就可以利用 Node.js 来读取该链接。读取文件系统上的链接将解析到链接实际链接的位置。这是通过fs.readlink功能完成的。fs.readlink函数接受两个参数:符号链接的路径和回调。回调将包含两个参数:一个错误(如果发生的话)和符号链接或硬链接解析的文件路径的字符串。与其他方法一样,这个函数有一个同步版本,它直接返回错误或结果字符串,而不使用回调函数。
3-9.更改文件权限
问题
在 Node.js 应用中,您需要控制文件系统中文件和目录的访问和权限级别。
解决办法
为了更改文件的权限,您必须利用操作系统使用的相同功能集来执行相同的操作。文件的标准规则集由访问级别和文件的所有权决定,访问级别是通过文件在文件系统上注册的模式授予的。比方说,你有一个在你的应用中使用的文件;您可能希望使它只对操作系统上的个人可读。这很容易做到,如清单 3-32 所示。本解决方案中显示的其他示例强调了个人访问文件的几种不同模式。稍后你会看到所有不同的可能性,你可以改变文件的模式。
清单 3-32 。在 Node.js 中更改文件权限
/**
* Altering file permissions
*/
var fs = require('fs'),
file = '3-9/file.txt';
//CHANGING MODES chmod
//hidden file
//-rwSr-S--T 1 cgack cgack 4 May 5 11:50 file.txt
fs.chmod(file, 4000, function(err) {
if (err) throw err;
});
//individual write
//--w------- 1 cgack cgack 4 May 5 11:50 file.txt
fs.chmod(file, 0200, function(err) {
if (err) throw err;
});
//individual execute
//---x------ 1 cgack cgack 4 May 5 11:50 file.txt
fs.chmod(file, 0100, function(err) {
if (err) throw err;
});
//individual write + execute
//--wx------ 1 cgack cgack 4 May 5 11:50 file.txt
fs.chmod(file, 0300, function(err) {
if (err) throw err;
});
//CHANGING OWNERS chown
// requires root access
//--wx------ 1 root root 4 May 5 11:50 file.txt
fs.chown(file, 0 /* root */, 0, function(err) {
if (err) throw err;
});
//--wx------ 1 cgack cgack 4 May 5 11:50 file.txt
fs.chown(file, 1000, 1000, function(err) {
if (err) throw err;
});清单中还显示了更改文件所有者的能力。您可以看到,通过使用fs.chown函数,文件的所有权被转移到 root,然后很容易地回到我的个人用户。
它是如何工作的
当您打算更改文件或目录的权限时,您可能首先需要更改所有权。在 Node.js 中,文件系统的所有权由fs.chown函数及其同步对应函数fs.chownSync决定。fs.chown函数接受四个参数。首先,您必须为您希望对其执行所有权变更的文件提供函数。其次,您必须提供系统上用户 ID 的整数。第三,添加该用户所属的组 ID 的整数。最后,该函数接受回调函数,回调函数将传递发生的任何错误。
文件名应该是显而易见的,因为您可能知道目标,或者在 Node.js 应用中提供一个需要更改所有权的文件。但是您可能没有记住您希望授予访问权限的所有用户的用户 ID 或组 ID。如果您想获得这些 ID 号,您可以在终端中使用以下命令。
清单 3-33 。通过终端确定用户和组 ID 号
$ id –u <username> #username user id
$ id –g <username> #username group id使用chown会直接改变文件系统上文件的所有者。您会看到,如$ ls –l所述,将 file.txt 中的所有者更改为 root 用户,这表明该文件被更改为由 root 用户//--wx------ 1 root root 4 May 5 11:50 file.txt所拥有。当然,对于我的用户 cgack 的所有权变更来说也是如此。需要注意的是,要更改文件的所有权,您需要以 root 权限进行操作。这意味着在这个例子中,您应该以$ sudo node 3-9-1.js的身份运行应用文件。没有这个级别的权限,你会遇到一个权限错误:Error: EPERM, chown '3-9/file.txt'。
一旦更改了文件的所有权,您可能仍然希望显式设置与该文件相关联的权限。在该解决方案中,您看到示例中的文件已从隐藏文件更改为单独写入、单独执行,然后是组合的单独写入+执行权限。设置这些相当简单,因为fs.chmod函数会改变文件的访问模式。这个函数,fs.chmod,接受三个参数:文件名、八进制权限代码的整数值,以及一个回调函数来传递发生的任何错误。
决定权限的八进制代码被分成几个部分。第一个数字代表授予“其他”用户的权限。第二个数字代表组级别的访问权限。第三和第四个分别代表个人用户访问和系统级访问。除了系统级访问权限(表示文件是隐藏的、存档的还是系统文件)之外,可能的值为 1(表示执行)、2(表示写入)和 4(表示读取)。完整列表如列表 3-34 所示。
清单 3-34 。文件访问设置
4000 Hidden file
2000 System file
1000 Archive bit
0400 Individual read
0200 Individual write
0100 Individual execute
0040 Group read
0020 Group write
0010 Group execute
0004 Other read
0002 Other write
0001 Other execute这些值都可以组合在一起,正如您在解决方案中看到的那样,通过利用0300 = 0200 and 0100来授权单独的写和执行。因此,个人的完全访问权限将设置在0700 = 0400 and 0200 and 0100。您可以看到,这个功能的全部范围,授予所有用户和组读、写和执行的完全访问权,将是0777。
有了这些工具,您应该能够在 Node.js 中改变访问级别和文件级别所有权。
3-10.查看文件修改
问题
您希望监视 Node.js 中文件的所有修改。
解决办法
如果您希望能够获得尽可能多的信息,并考虑对文件系统中的文件进行更改,那么 Node.js 的文件系统模块有一套可能行得通的解决方案。这种文件系统监控有两种方法。一种是使用fs.watchFile方法,该方法将返回您正在查看的以前和当前文件的整个 file stat 对象。第二种是较新的方法,在 3-4 节中简要提到:fs.watch。要查看这两个选项的运行情况,查看对任意文件的更改,只需看看清单 3-35 。
清单 3-35 。监视文件更改的两种方法
/**
* Watching files for modifications
*/
var fs = require('fs'),
path = '3-10/file.txt';
fs.watchFile(path, function(current, previous){
for (var key in current) {
if (current[key] !== previous[key]) {
console.log(key + ' altered. prev: ' + previous[key] + ' curr: ' + current[key]);
}
}
});
fs.watch(path, function(event, filename){
if (filename) {
console.log(filename + ' : ' + event);
} else {
//Macs don't pass the filename
console.log(path + ' : ' + event);
}
});这将产生一个结果,如果你改变文件的内容到某种简单的程度,它将看起来类似于清单 3-36 中显示的控制台输出。
清单 3-36 。观看文件
$ node 3-10-1.js
3-10/file.txt : change
3-10/file.txt : change
size altered. prev: 14 curr: 19
atime altered. prev: Sun May 05 2013 14:04:37 GMT-0400 (EDT) curr: Sun May 05 2013 14:07:23 GMT-0400 (EDT)
mtime altered. prev: Sun May 05 2013 14:04:37 GMT-0400 (EDT) curr: Sun May 05 2013 14:07:22 GMT-0400 (EDT)
ctime altered. prev: Sun May 05 2013 14:04:37 GMT-0400 (EDT) curr: Sun May 05 2013 14:07:22 GMT-0400 (EDT)它是如何工作的
首先,你看到的是fs.watchFile函数。这个函数接受一个路径参数和一个回调函数,回调函数将提供您正在查看的文件的当前和以前的状态。在文件上执行长轮询fs.stat调用可以做到这一点。这是由可选的第二个参数配置的,它是一个 options 对象,默认为{ persistent: true, interval: 5007 },允许连续或持久的时间间隔进行轮询。
fs.watchFile函数创建一个新的StatWatcher对象(清单 3-37 ),它在设定的时间间隔轮询文件的 stat 对象。如果在StatWatcher上发生变化,这些统计信息将在监听器回调中返回。这将返回文件 stat 的以前和当前版本。即使该文件以前不存在,它也会显示一个添加日期stat.atime,值为:Wed Dec 31 1969 19:00:00 GMT-0500(EST)(UNIX 纪元的开始)。
清单 3-37 。StatWatcher EventEmitter
function StatWatcher() {
EventEmitter.call(this);
var self = this;
this._handle = new binding.StatWatcher();
// uv_fs_poll is a little more powerful than ev_stat but we curb it for
// the sake of backwards compatibility
var oldStatus = -1;
this._handle.onchange = function(current, previous, newStatus) {
if (oldStatus === -1 &&
newStatus === -1 &&
current.nlink === previous.nlink) return;
oldStatus = newStatus;
self.emit('change', current, previous);
};
this._handle.onstop = function() {
self.emit('stop');
};
}
util.inherits(StatWatcher, EventEmitter);
StatWatcher.prototype.start = function(filename, persistent, interval) {
nullCheck(filename);
this._handle.start(pathModule._makeLong(filename), persistent, interval);
};
StatWatcher.prototype.stop = function() {
this._handle.stop();
};在该解决方案中,您可以看到,每次侦听器进行文件归档时,都会遍历当前 stat 并与之前的 stat 进行比较。然后对于当前对象中与文件的前一个 stat 中不同的每个关键点(参见清单 3-38 )。
清单 3-38 。遍历文件以找到改变的状态。
for (var key in current) {
if (current[key] !== previous[key]) {
console.log(key + ' altered. prev: ' + previous[key] + ' curr: ' + current[key]);
}
}在此、fs.watchFile和fs.watch之间进行选择的选项并不明确。这两种解决方案仍被认为不稳定。虽然fs.watchFile可以返回被监视文件的完整 stat 细节,但它受限于轮询功能,因此返回那个fs.watch要慢得多,您将在下面看到。
fs.watch函数创建一个FSWatcher,如清单 3-39 所示,它是一个 Node.js EventEmitter,与StatWatcher EventEmitter类似,当检测到文件修改时,它会产生并发出一个 change 事件。
清单 3-39 。创建新的 FSWatcher
watcher = new FSWatcher();
watcher.start(filename, options.persistent);
if (listener) {
watcher.addListener('change', listener);
}FSWatcher 在文件或目录上创建一个新的FSEvent句柄。然后FSWatcher绑定到这个句柄的change事件(见清单 3-40 )。
清单 3-40 。FSWatcher
function FSWatcher() {
EventEmitter.call(this);
var self = this;
var FSEvent = process.binding('fs_event_wrap').FSEvent;
this._handle = new FSEvent();
this._handle.owner = this;
this._handle.onchange = function(status, event, filename) {
if (status) {
self._handle.close();
self.emit('error', errnoException(process._errno, 'watch'));
} else {
self.emit('change', event, filename);
}
};
}
util.inherits(FSWatcher, EventEmitter);
FSWatcher.prototype.start = function(filename, persistent) {
nullCheck(filename);
var r = this._handle.start(pathModule._makeLong(filename), persistent);
if (r) {
this._handle.close();
throw errnoException(process._errno, 'watch');
}
};通过调用文件或目录上的fs.watch函数创建的FSWatcher将发出两个事件之一:错误或变更。在清单 3-40 中,变化事件是你的监听器函数所绑定的。这个回调提供了一个事件和一个文件名(或目录),这个事件发生在这个文件上。事件可以是"changed"或"renamed"。这缺少 fs.watchFile 函数的信息,正如您所看到的,该函数为更改的文件提供了一个完整的 stat 对象。``