定义需求
- 程序在命令行运行。这就意味着程序要么通过node命令来执行,要么直接执行,然后通过终端提供交互给用户进行输入、输出。
- 程序启动后,需要显示当前目录下列表。
- 选择某个文件时,程序需要显示该文件内容。
- 选择一个目录时,程序需要显示该目录下的信息。
- 运行结束后程序退出。
细分步骤
1、创建模块
2、决定采用同步的fs还是异步的fs
3、理解什么是流(Stream)
4、实现输入、输出
5、重构
6、使用fs进行文件交互
7、完成。
创建模块
创建一个简单的package.json
虽然我们整个小项目只用到了fs模块API,但是尽量遵守规范
// package.json
{
"name": "file-explorer",
"version": "0.0.1",
"description": "A commond-file file explorer"
}
NPM遵循一个名为semver的版本控制标准。
可以运行npm install
验证package.json文件是否有效。
同步还是异步
fs模块是唯一一个同时提供同步和异步API的模块
要在单线程中创建能够处理高并发的高效程序,就得此采用异步、事件驱动的程序
获取当前目录的文件列表:
var fs= require('fs');
fs.readdir(__dirname,function(err,files){
console.log(files);
});
结果:
理解什么是流
console.log会输出到控制台。事实上,console.log内部做了:它在指定的字符串后面加上\n(换行)字符,并将其写到stdout流中。
process全局对象中包含了三个流对象,分别对应三个UNIX标准流:
stdin:标准输入 (可读流)
stdout:标准输出 (可写流)
stderr;标准错误 (可写流)
stdin流默认是暂定的
Stream对象和EventEmitter对象很像,实际上,前者继承后者。
输入和输出
列出当前目录下的文件,然后等待用户输入。
var fs= require('fs');
fs.readdir(process.cwd(),function(err,files){
console.log('');
if (!files.length) {
return console.log(' \033[31m No files to show!\033[39m\n');
}
console.log(' Select which file or directory you want to see\n');
function file(i) {
var filename = files[i];
fs.stat(__dirname + '/' + filename,function(err, stat) {
if (stat.isDirectory()) {
console.log(' ' + i + ' \033[36m' + filename + '\/033[39m');
} else {
console.log(' ' + i + ' \033[90m' + filename + '\033[39m');
}
i++;
if (i == files.length) {
console.log('');
process.stdout.write(' \033[33mEnter your choice: \033[39m');
process.stdin.resume();
} else {
file(i);
}
});
}
file(0);
});
文本周围的\033[31m 和\033[39m是为了让文本呈现为红色。
file.stat会给出文件或者目录的元数据
用stat.isDirectory()判断是否是目录
如果所有文件都执行完毕,此时提示用户进行选择。
process.stdin.resume();
等待用户输入
process.stdin.setEncoding('utf8');
设置流编码为utf8,这样就能支持特殊字符了
要是还有未处理的文件,就递归调用该函数来处理:file(i)
直到列出所有文件、用户输入完毕后,紧接着下一步串行处理。
重构
由于我们书写的代码都是异步的,因此,随着函数量的增长(特别是流控制层的增加,过多的函数嵌套会让程序的可读性变差)。可以为每一个异步操作预定一个函数。
首先,抽离出以一个读取stdin函数
console.log('');
i++;
if (i == files.length) {
read();
} else {
file(i);
}
function read() {
console.log('');
process.stdout.write(' \033[33mEnter your choice: \033[39m');
process.stdin.resume();
process.stdin.setEncoding('utf8');
}
设置了stdin编码后,就要监听其事件。
function read() {
console.log('');
stdout.write(' \033[33mEnter your choice: \033[39m');
stdin.resume();
stdin.setEncoding('utf8');
stdin.on('data',option);
}
function option(data) {
if (!files[Number(data)]) {
stdout.write(' \033[31mEnter your choice: \033[39m');
} else {
stdin.pause();
}
}
用fs进行文件操作
将目录保存下来,读取文件,使用正则表达式添加一下辅助后将文件内容输出。
function option(data) {
var filename = files[Number(data)];
if (stats[Number(data)].isDirectory()) { // stats[i]保存文件的信息包括属性
fs.readdir(__dirname + '/' + filename, function(err,files) {
console.log('');
console.log(' (' + files.length + ' files)');
files.forEach(function(file){
console.log(' - ' + file);
})
});
console.log('');
} else {
fs.readFile(__dirname + '/' + filename, 'utf8', function(err,data) {
console.log('');
console.log('\033[90m' + data.replace(/(.*)/g, ' $1') + '\033[39m');
});
}
}
完整代码:
var fs= require('fs'),
stdin = process.stdin,
stdout = process.stdout;
fs.readdir(process.cwd(),function(err,files){
var stats = []; // 缓存读过的目录
console.log('');
if (!files.length) {
return console.log(' \033[31m No files to show!\033[39m\n');
}
console.log(' Select which file or directory you want to see\n');
function file(i) {
var filename = files[i];
fs.stat(__dirname + '/' + filename,function(err, stat) {
stats[i] = stat;
if (stat.isDirectory()) {
console.log(' ' + i + ' \033[36m' + filename + '\/033[39m');
} else {
console.log(' ' + i + ' \033[90m' + filename + '\033[39m');
}
i++;
if (i == files.length) {
read();
} else {
file(i);
}
});
}
function read() {
console.log('');
stdout.write(' \033[33mEnter your choice: \033[39m');
stdin.resume();
stdin.setEncoding('utf8');
stdin.on('data',option);
}
function option(data) {
var filename = files[Number(data)];
if (stats[Number(data)].isDirectory()) { // stats[i]保存文件的信息包括属性
fs.readdir(__dirname + '/' + filename, function(err,files) {
console.log('');
console.log(' (' + files.length + ' files)');
files.forEach(function(file){
console.log(' - ' + file);
})
});
console.log('');
} else {
fs.readFile(__dirname + '/' + filename, 'utf8', function(err,data) {
console.log('');
console.log('\033[90m' + data.replace(/(.*)/g, ' $1') + '\033[39m');
});
}
}
file(0);
});
argv
process.argv包含了所有Node程序运行时的参数值
要获取真正的参数,需要去掉数组中的前两个元素
工作目录
__dirname用来获取执行文件时该文件在文件系统中所在的目录。
要获取当前工作目录,可以调用process.cwd()方法。
Node还提供了process.chdir方法,允许灵活地更改工作目录
process.chdir('/');
process.cwd(); // /
环境变量
Node允许通过process.env.NODE_ENV来访问shell环境下的变量名。
NODE_ENV用来控制程序运行在开发模式下还是生产模式下。
退出
process.exit(1);
信号
进程和操作系统进行通信的其中一种方式就是通过信号。比如,要让进程终止,可以发送SIGKILL信号。
在Node中是通过在process对象上以事件分发的形式来发送信号的:
process.on('SIGKILL',function(){});
Stream
fs.createReadStream方法允许为一个文件创建一个可读的Stream对象。
读文件的时候,我们可能会用:
fs.readFire('my.txt',function(err,data){
// 对文件进行处理
})
从代码中我们可以看到,回调函数必须等到整个文件读取完毕、载入RAM、可用的情况下才会触发。
而利用Stream对象,每次会读取可变大小的内容块,并且每次读取会触发回调函数:
const fs = require('fs');
const stream = fs.createReadStream('index.js');
stream.on('data',function(chunk){
process.stdout.write(chunk);
});
stream.on('end',function(chunk){
process.stdout.write('\n------\n');
})
Stream的能力很强大。假设有个很大的视频文件需要上传至某个web服务。此时 无需在读取完整的视频内容后再开始上传,使用Stream就可以大大提速上传过程。
同理,记录日志记录的例子也很有效。这个时候可以使用fs.WriteStream的例子。打开文件操作只做一次,写如每个日志项的时候都调用.write方法。
watch
Node允许监视文件或目录是否发生变化。监视意味着当系统中的文件(或目录)发生变化时,会分发一个事件,然后触发相应的回调函数。
fs.watchFile();
fs.watch();
参考《了不起的Node.js》