Node.js实现了基本的文件操作,但是像拷贝这种高级功能却没有实现,我们来实现一个文件拷贝程序。

与copy命令类似,程序需要接受源文件路径与目标文件路径链两个参数。

小文件拷贝

1
2
3
4
5
6
7
8
9
10
11
var fs = require('fs');

function copy(src, dst) {
fs.writeFileSync(dst, fs.readFileSync(src));
}

function main(argv) {
copy(argv[0], argv[1]);
}

main(process.argv.slice(2));

以上程序使用fs.readFileSync从源文件读取文件内容,并使用fs.writeFileSync将文件内容写入目标路径。

process是一个全局变量,可通过process.argv获得命令行参数。由于argv[0]固定等于Node.js执行程序的绝对路径,argv[1]固定等于主模块的绝对路径,因此第一个命令行参数是从argv[2]这个位置开始的。

大文件拷贝

上边的程序拷贝一些小文件没问题,但是这种一次性把所有文件内容都读取到内存中后再一次性写入磁盘的方式不适合拷贝大文件,内存会爆仓。对于大文件,我们只能读一点写一点,直到完成拷贝。

1
2
3
4
5
6
7
8
9
10
11
var fs = require('fs');

function copy(src, dst) {
fs.createReadStream(src).pipe(fs.createWriteStream(dst));
}

function main(argv) {
copy(argv[0], argv[1]);
}

main(process.argv.slice(2));

以上程序使用fs.createReadStream创建了一个源文件的只读数据流,并使用fs.createWriteStream创建了一个目标文件的只写文件流,并且用pipe方法把两个数据流链接了起来。

开发命令行工具

虽然这个命令行工具很多余,但是我们可以从中学到如何开发命令行工具。

首先,在头部添加一行shebang,表示用后面的路径所示的程序来执行当前文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
 #! /usr/bin/env node
var fs = require('fs');

function copy(src, dst) {
fs.writeFileSync(dst, fs.readFileSync(src));
}

function main(argv) {
copy(argv[0], argv[1]);
console.log("copy successfully!")
}

main(process.argv.slice(2));

然后我们需要新建一个package.json文件,内容如下:

1
2
3
4
5
6
7
8
9
{
"name": "copy",
"version": "0.0.1",
"description": "copy one file to the other file",
"preferGlobal": "true",
"bin": { "copy": "copy.js" },
"author": "virgil",
"engines": { "node": "*" }
}

上面的bin字段中,copy是命令行的名字,copy.js是要执行的文件,然后需要使用npm来使命令copy可以在整个系统下执行。在当前目录下执行:

1
2
npm link
//解除绑定 npm unlink

copy已经完美运行了

1
2
copy a.txt b.txt
copy successfully!

通过npm可以快速将创建的命令行工具发布到网上,而需要的用户也可以很快速的获取。

1
2
npm publish
npm install

强化版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
 #! /usr/bin/env node
var fs = require('fs'),
path = require('path'),
out = process.stdout,
src = process.argv.slice(2)[0],
dst = process.argv.slice(2)[1];

var readStream = fs.createReadStream(src);
var writeStream = fs.createWriteStream(dst);

var stat = fs.statSync(src);
var totalSize = stat.size;
var passedLength = 0;
var lastSize = 0;
var startTime = Date.now();

readStream.on('data', function(chunk) {
passedLength += chunk.length;
if (writeStream.write(chunk) === false) {
readStream.pause();
}
});

readStream.on('end', function() {
writeStream.end();
});

writeStream.on('drain', function() {
readStream.resume();
});

setTimeout(function show() {
var percent = Math.ceil((passedLength / totalSize) * 100);
var size = Math.ceil(passedLength / 1000000);
var diff = size - lastSize;
lastSize = size;
out.clearLine();
out.cursorTo(0);
out.write('已完成' + size + 'MB, ' + percent + '%, 速度:' + diff * 2 + 'MB/s');
if (passedLength < totalSize) {
setTimeout(show, 500);
} else {
var endTime = Date.now();
console.log();
console.log('共用时:' + (endTime - startTime) / 1000 + '秒。');
}
}, 500);