本文大部分是参考阮一峰的文章,只是额外记录在windows下尝试的笔记。
原文地址:http://www.ruanyifeng.com/blog/2015/05/command-line-with-node.html。
我在windows系统试了下,有些地方感觉和原文不一样,不知道是不是自己理解不到位,还是因为系统不同的缘故。
我的nodejs安装目录是D:\nodejs,在下面创建了个项目test。把hello.js放在D:\nodejs\test
一、可执行脚本(Window和Linux有差异)
1)步骤
首先,使用 JavaScript 语言,写一个可执行脚本 hello.js 。(#!/usr/bin/env node 用来)
#!/usr/bin/env node
console.log('hello world');
现在,hello 就可以执行了。进入到test文件夹,输入以下命令:
(原文中写的是直接输入./hello,我尝试那样做,但是是直接在浏览器打开一个文件,把js内容直接输出。)
node hello
执行结果如下:
如果想把 hello 前面的路径去除,可以将 hello 的路径加入环境变量 PATH。但是,另一种更好的做法,是在当前目录(D:\nodejs\test)下新建 package.json ,写入下面的内容。
{
"name": "hello",
"bin": {
"hello": "hello.js" //原文中写的是"hello": "hello"
}
}
然后执行npm link,如下图所示
执行完后,会在{prefix}/node_modules下生成test文件夹的快捷方式({prefix}是指全局路径,我的全局路径是D:\nodejs\node_global)。另外也会生成hello.cmd、hello.sh放到{prefix}文件夹下,因此在任意路径输入hello都能够执行脚本,如下图所示:
package.json的bin路径原文中写的是"hello": "hello",我尝试了下,发现会有错误,错误提示如下:
2)#!/usr/bin/env node
#!/usr/bin/env node是Unix和Linux脚本语言的第一行,目的就是指出,你想要你的这个文件中的代码用什么可执行程序去运行它。除了#!/usr/bin/env node,还有!/usr/bin/node
!/usr/bin/node是告诉操作系统执行这个脚本的时候,调用/usr/bin下的node解释器;
!/usr/bin/env node这种用法是为了防止操作系统用户没有将node装在默认的/usr/bin路径里。当系统看到这一行的时候,首先会到env设置里查找node的安装路径,再调用对应路径下的解释器程序完成操作。
!/usr/bin/node相当于写死了node路径;
!/usr/bin/env node会去环境设置寻找node目录,推荐这种写法
思考:这个在Windows系统上能起作用吗?是不是就是因为这一行在Windows系统上不起作用导致我直接执行./hello时没有效果?
二、命令行参数的原始写法(Window和Linux没差异)
命令行参数可以用系统变量 process.argv 获取。
下面是一个脚本 hello 。
#!/usr/bin/env node
console.log('hello ', process.argv[2]);
执行时,直接在脚本文件后面,加上参数即可。用法如下:
node hello tom
结果如下:
上面代码中, process.argv 是 ['node', '/path/to/hello', 'tom'] 。
三、新建进程(Window和Linux没差别)
脚本可以通过 child_process 模块新建子进程,从而执行系统命令。
#!/usr/bin/env node
var name = process.argv[2];
var exec = require('child_process').exec;
var child = exec('echo hello ' + name, function(err, stdout, stderr) {
if (err) throw err;
console.log(stdout);
});
用法如下:
node hello tom
结果如下:
child_process详细学习可参考:nodejs中文网的http://nodejs.cn/api/child_process.html
四、shelljs 模块(Window和Linux没差异)shelljs 模块重新包装了 child_process,调用系统命令更加方便。它需要安装后使用。
npm install --save shelljs
shelljs会被安装到项目目录(D:\nodejs\test)下的node_moudles文件夹。
然后,改写脚本。
#!/usr/bin/env node
var name = process.argv[2];
var shell = require("shelljs");
shell.exec("echo hello " + name);
上面代码是 shelljs 的本地模式,即通过 exec 方法执行 shell 命令。此外还有全局模式,允许直接在脚本中写 shell 命令。
require('shelljs/global');
if (!which('git')) {
echo('Sorry, this script requires git');
exit(1);
}
mkdir('-p', 'out/Release');
cp('-R', 'stuff/*', 'out/Release');
cd('lib');
ls('*.js').forEach(function(file) {
sed('-i', 'BUILD_VERSION', 'v0.1.2', file);
sed('-i', /.*REMOVE_THIS_LINE.*\n/, '', file);
sed('-i', /.*REPLACE_LINE_WITH_MACRO.*\n/, cat('macro.js'), file);
});
cd('..');
if (exec('git commit -am "Auto-commit"').code !== 0) {
echo('Error: Git commit failed');
exit(1);
}
shelljs的详细学习可参考:https://www.npmjs.com/package/shelljs
五、yargs 模块(Window和Linux没差别)
shelljs 只解决了如何调用 shell 命令,而 yargs 模块能够解决如何处理命令行参数。它也需要安装。
npm install --save yargs
yargs 模块提供 argv 对象,用来读取命令行参数。请看改写后的 hello :
#!/usr/bin/env node
var argv = require('yargs').argv;
console.log('process.argv:', process.argv);
console.log('yargs argv: ', argv);
console.log('argv.name:', argv.name);
使用时,下面两种用法都可以。
node hello --name=tom
或 node hello --name tom
从上图中可以看到process.argv 的原始返回值是一个数组。而yargs将结果改为对象,每个参数项就是一个键值对。因此可以直接通过require('yargs').argv.name来访问name参数。
如果将 argv.name 改成 argv.n,就可以使用一个字母的短参数形式了。用法如下:node hello -n tom
可以使用 alias 方法,指定 name 是 n 的别名。
#!/usr/bin/env node
var argv = require('yargs')
.alias('n', 'name')
.argv;
console.log('hello ', argv.n);
这样一来,短参数和长参数就都可以使用了。(短参数-n或--n,长参数-name或--name好像都是可以访问到对应变量。但--后面一般跟长的完整名字,-后面一般跟简写大多数是一个字母)。效果如下图:
argv 对象有一个下划线(_)属性,可以获取非连词线开头的参数。
#!/usr/bin/env node
var argv = require('yargs').argv;
console.log('hello ', argv.n);
console.log(argv._);
用法如下:
node hello A -n tom B C
效果如下图:
六、命令行参数的配置(Window和Linux没差别)
yargs 模块还提供3个方法,用来配置命令行参数
demand:是否必选
default:默认值
describe:提示
#!/usr/bin/env node
var argv = require('yargs')
.demand(['n'])
.default({n: 'tom'})
.describe({n: 'your name'})
.argv;
console.log('hello ', argv.n);
上面代码指定 n 参数不可省略,默认值为 tom,并给出一行提示。输入node hello --help时,会出现这些信息:
options 方法允许将所有这些配置写进一个对象。
有时,某些参数不需要值,只起到一个开关作用,这时可以用 boolean 方法指定这些参数返回布尔值。
#!/usr/bin/env node
var argv = require('yargs')
.boolean(['n'])
.argv;
console.log('hello ', argv.n);
上面代码中,参数 n 总是返回一个布尔值,用法如下:
boolean 方法也可以作为属性,写入 option 对象。
#!/usr/bin/env node
var argv = require('yargs')
.option('n', {
boolean: true
})
.argv;
console.log('hello ', argv.n);
七、帮助信息(Window和Linux没差别)
yargs 模块提供以下方法,生成帮助信息。
usage:用法格式
example:提供例子
help:显示帮助信息
epilog:出现在帮助信息的结尾
var argv = require('yargs')
.option('f', {
alias : 'name',
demand: true,
default: 'tom',
describe: 'your name',
type: 'string'
})
.usage('Usage: hello [options]')
.example('hello -n tom', 'say hello to Tom')
.help('h')
.alias('h', 'help')
.epilog('copyright 2015')
.argv;
console.log('hello ', argv.n);
执行结果如下:
八、子命令(Window和Linux没差别)
yargs 模块还允许通过 command 方法,设置 Git 风格的子命令。
#!/usr/bin/env node
var yargs = require('yargs')
.command("morning", "good morning", function (yargs) {
console.log("Good Morning");
})
.command("evening", "good evening", function (yargs) {
console.log("Good Evening");
})
var argv = yargs.argv; //必须要访问argv属性,或通过parse()解析参数,前面的命令才会得到执行
console.log('hello ', argv.n);
用法如下:
可以将这个功能与 shellojs 模块结合起来。
#!/usr/bin/env node
require('shelljs/global');
var argv = require('yargs')
.command("morning", "good morning", function (yargs) {
echo("Good Morning");
})
.command("evening", "good evening", function (yargs) {
echo("Good Evening");
})
.argv;
echo("hello", argv.n);
每个子命令往往有自己的参数,这时就需要在回调函数中单独指定。回调函数中,要先用 reset 方法重置 yargs 对象。
#!/usr/bin/env node
require('shelljs/global');
var argv = require('yargs')
.command("morning", "good morning", function (yargs) {
echo("Good Morning");
var argv = yargs.reset()
.option("m", {
alias: "message",
description: "provide any sentence"
})
.help("h")
.alias("h", "help")
.argv;
echo(argv.m);
})
.argv;
echo(argv.m); //和morning命令内部获取的值一样
用法如下:
node hello morning -m "Are you hungry?"
注意-m后面的字符串要用双引号,如果是单引号或不加,只能获取到Are。
效果如下图:
虽然在命令morning内部和外部都能获取m参数,但是加上morning命令时,对命令的解析会有区别。
九、其他事项
1)重定向
Unix 允许程序之间使用管道重定向数据(Windows也可以)。
重定向描述:
假设您想要一张 images 目录中所有以 .png 结尾的文件[6]列表。该列表非常长,因此您会想把它先放到一个文件中,然后在有空的时候查看。您可以输入下述命令:
$ ls images/*.png 1>file_list
这表示把该命令的标准输出(1)重定向到(>)file_list 文件。其中的 > 操作符是输出重定向符。如果要重定向到的文件不存在,它将被创建;不过如果它已经存在,那么它先前的内容将被覆盖。不过,该操作符默认的描述符就是标准输出,因此就不用在命令行上特意指出。所以,上述命令可以简化为:
管道描述:
管道是指把一个命令的输出作为下一个命令的输入。管道在某种程度上是输入和输出重定向的结合。其原理同物理管道类似:一个进程向管道的一端发送数据,而另一个进程从该管道的另一端读取数据。
脚本可以通过监听标准输入的data 事件,获取重定向的数据。
process.stdin.resume();
process.stdin.setEncoding('utf8');
process.stdin.on('data', function(data) {
process.stdout.write('hello ' + data);
});
下面是用法。
echo Tom Zhang | node hello
2)系统信号
操作系统可以向执行中的进程发送信号,process 对象能够监听信号事件。
process.on('SIGINT', function() {
console.log('收到 SIGINT 信号。');
});
console.log('试着按下 ctrl + C');
/*为了避免执行完后退出当前脚本,加了个定时任务*/
setTimeout(function() {
console.log('end');
}, 50000);
输入node hello后,效果如下:
我在命令行窗口按下了Ctrl + C,收到信息“收到SIGINT信号”。如下图: