Skip to content

Commit 4ce58af

Browse files
committed
Node的readline (逐行读取)
1 parent 065197a commit 4ce58af

File tree

9 files changed

+506
-1
lines changed

9 files changed

+506
-1
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- Lesson3 - [Node模块与npm](/lesson3)
77
- Lesson4 - [搭建静态文件服务器](/lesson4)
88
- Lesson5 - [Node中的stream (流)](/lesson5)
9+
- Lesson6 - [Node的readline (逐行读取)](/lesson6)
910

1011
## Node相关入门资料
1112

lesson3/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ Is this ok? (yes) yes
212212

213213
```bsah
214214
$ npm adduser
215-
Username: liuxing
215+
Username: cosyer
216216
Password:
217217
Email: (this IS public) [email protected]
218218
```

lesson6/README.md

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# Node的readline (逐行读取)
2+
> `readline` 模块提供了一个接口,用于从可读流(如 process.stdin)读取数据,每次读取一行
3+
4+
先来看这个基本示例:
5+
```javascript
6+
const readline = require('readline');
7+
8+
const rl = readline.createInterface({
9+
input: process.stdin,
10+
output: process.stdout
11+
});
12+
13+
rl.question('你叫什么名字?', (answer) => {
14+
// 对答案进行处理
15+
console.log(`你好:${answer}`);
16+
17+
rl.close();
18+
});
19+
```
20+
21+
在命令行中执行`node hello.js`将出现如下结果:
22+
23+
```bash
24+
$ node hello.js
25+
你叫什么名字?cosyer
26+
早上好,cosyer
27+
```
28+
29+
通过这个示例,我们可以发现`readline`模块可以方便的实现命令行的交互功能。
30+
31+
下面我们先来了解`readline`的基本用法,然后再实现一个有趣的小功能。
32+
33+
## readline基本用法
34+
35+
`readline.Interface` 类的实例是使用 `readline.createInterface()` 方法构造的。 每个实例都关联一个 `input` [可读流](http://nodejs.cn/api/stream.html#stream_readable_streams)和一个 `output` [可写流](http://nodejs.cn/api/stream.html#stream_writable_streams)`output` 流用于为到达的用户输入打印提示,且从 `input` 流读取
36+
37+
### 创建Readline实例
38+
39+
```javascript
40+
readline.createInterface(options)
41+
```
42+
43+
创建一个`readline`的接口实例. 接受一个Object类型参数,可传递以下几个值:
44+
45+
- `input` - 要监听的可读流 (必需)
46+
- `output` - 要写入 readline 的可写流 (必须).
47+
- `completer` - 用于 Tab 自动补全的可选函数。(不常用)
48+
- `terminal` - 如果希望 input 和 output 流像 TTY 一样对待,那么传递参数 true ,并且经由 ANSI/VT100 转码。 默认情况下检查 isTTY 是否在 output 流上实例化。(不常用)
49+
50+
### 方法
51+
52+
- `rl.close()` 关闭接口实例 (Interface instance), 放弃控制输入输出流。”close” 事件会被触发
53+
- `rl.pause()`暂停 readline 的输入流 (input stream), 如果有需要稍后还可以恢复。
54+
- `rl.prompt([preserveCursor])` 为用户输入准备好readline,将现有的setPrompt选项放到新的一行,让用户有一个新的地方开始输入。将preserveCursor设为true来防止光标位置被重新设定成0。
55+
- `rl.question(query, callback)` 预先提示指定的query,然后用户应答后触发指定的callback。 显示指定的query给用户后,当用户的应答被输入后,就触发了指定的callback
56+
- `rl.resume() `恢复 readline 的输入流 (input stream).
57+
- `rl.setPrompt(prompt)` 用于设置每当 `rl.prompt()` 被调用时要被写入到 `output` 的提示。
58+
- `rl.write(data[, key]) ``data` 或一个由 `key` 指定的按键序列写入到 `output`
59+
60+
### 事件
61+
62+
- `line`事件 :在 input 流接受了一个` \n` 时触发,通常在用户敲击回车或者返回时接收。 这是一个监听用户输入的利器。
63+
- `pause`事件: 输入流被暂停就会触发。 同样当输入流未被暂停,但收到 SIGCONT 也会触发。 (详见 SIGTSTP 和 SIGCONT 事件)
64+
- `resume`事件:只要输入流重新启用就会触发
65+
- `close`事件:当 close() 被调用时触发。 当 input流接收到`end`事件时也会被触发. 流接收到表示结束传输的 `<ctrl>-D`,收到表示 SIGINT 的 `<ctrl>-C`,且 readline.Interface 实例上没有注册 SIGINT 事件监听器。
66+
67+
更多请参考[node中文网](http://nodejs.cn/api/readline.html)
68+
69+
### readline 实列
70+
71+
> 现在我们来实现一个命令行可交互的百度搜索
72+
>
73+
74+
#### 1.使用readline实现一个可交互的命令行
75+
76+
```javascript
77+
// 先来实现一个可交互命令行
78+
const rl = readline.createInterface({
79+
input: process.stdin,
80+
output: process.stdout,
81+
prompt: 'search>>> '
82+
})
83+
84+
rl.prompt()
85+
86+
rl.on('line', (line) => {
87+
console.log(line)
88+
rl.prompt()
89+
}).on('close', () => {
90+
console.log('再见!')
91+
process.exit(0)
92+
})
93+
```
94+
95+
`node index.js `运行这段代码出现可交互的命令行,等待我们输入,输入完成回车后会打印出输入的值
96+
97+
#### 2.使用http模块发起请求
98+
99+
*这里为了加深对原生http模块的理解我们没用第三方模块,有很多好用的第三方模块如:[request](https://github.com/request/request)[superagent](http://visionmedia.github.io/superagent/)*
100+
101+
```javascript
102+
...
103+
function search(words, callback) { // es6默认参数
104+
let options = {
105+
hostname: 'www.baidu.com',
106+
port: 80,
107+
path: `/s?wd=${encodeURI(words)}`,
108+
method: 'GET'
109+
}
110+
111+
const req = http.request(options, (res) => {
112+
// console.log(`STATUS: ${res.statusCode}`) //返回状态码
113+
// console.log(`HEADERS: ${JSON.stringify(res.headers, null, 4)}`) // 返回头部
114+
res.setEncoding('utf8') // 设置编码
115+
let body = ''
116+
res.on('data', (chunk) => { //监听 'data' 事件
117+
body+=chunk
118+
})
119+
res.on('end', ()=>{
120+
let $ = cheerio.load(body)
121+
$('.t a').each(function(i, el){
122+
console.log($(this).text(), $(this).attr('href'),'\n')
123+
})
124+
callback()
125+
})
126+
})
127+
req.end() // end方法结束请求
128+
}
129+
130+
...
131+
```
132+
133+
这里我们用http模块发起客户端请求,并打印出来搜索结果的标题与链接,如果你忘记了http模块的使用可以回头查看[了解并使用Http模块](http://www.xingxin.me/posts/58e73d13ab572f17b0297881),http模块是node相当重要的模块,后续我们还将继续学习。
134+
135+
这里我们用到了[cheerio](https://github.com/cheeriojs/cheerio)具体使用可参考其官网
136+
137+
#### 3. 实现命令行搜索
138+
到此,我们只需将命令行的代码与http请求的代码整合起来就可以完成这个小项目了
139+
140+
```javascript
141+
...
142+
rl.on('line', (line) => {
143+
search(line.trim(), ()=>{
144+
rl.prompt()
145+
})
146+
}).on('close', () => {
147+
console.log('再见!')
148+
process.exit(0)
149+
})
150+
...
151+
```
152+
153+
**源码地址: [github](https://github.com/ogilhinn/node-abc/tree/master/lesson6)**
154+
155+
现在在快命令行中试试我们一起完成的这个小案例吧!!!
156+
157+
*现在我们已经基本完成了命令行百度搜索这个小案例,我们还可为他加入更多的功能.。如,我们可以把它开发为全局命令行工具,只需要一个命令便可调用。如果有兴趣可以参考[Node命令行工具开发【看段子】](http://www.xingxin.me/posts/58cc8e617ed80d4b7974f3da)[node命令行小工具开发【翻译小工具】](http://www.xingxin.me/posts/58c9014ffc98493874be8301),建议大家都去试一试,实现更多有趣的功能*
158+
159+
## 相关链接
160+
- [Node中文网](http://nodejs.cn/api/readline.html)
161+
- [Node命令行工具开发【看段子】](http://www.xingxin.me/posts/58cc8e617ed80d4b7974f3da)
162+
- [node命令行小工具开发【翻译小工具】](http://www.xingxin.me/posts/58c9014ffc98493874be8301)
163+

lesson6/hello.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const readline = require('readline');
2+
3+
const rl = readline.createInterface({
4+
input: process.stdin,
5+
output: process.stdout
6+
});
7+
8+
rl.question('你叫什么名字?', (answer) => {
9+
// 对答案进行处理
10+
console.log(`早上好,${answer}`);
11+
12+
rl.close();
13+
});

lesson6/node命令行工具开发.md

+197
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
# node命令行工具开发
2+
3+
> NodeJs有许多命令行工具。它们全局安装,并提供一个命令供我们使用,完成相应的功能。 现在我们就用node来开发一个实用的命令行小工具
4+
5+
## 一.初探
6+
7+
### 一个最简单的命令行工具
8+
9+
1.首先我们新建一目录,然后执行`npm init`生成package.json文件
10+
11+
2.新建一bin目录并在目录下创建一个hi.js
12+
13+
```javascript
14+
#! /usr/bin/env node
15+
console.log("hi")
16+
```
17+
18+
执行`node hi.js`我们可以看到终端输出hi。。当然这并不是我们要的命令行工具,我们需要直接运行`hi`就可出现结果
19+
20+
3.现在我们告诉npm可执行文件是哪个,在package.json里添加如下信息:
21+
22+
```json
23+
"bin": {
24+
"hi": "bin/hi.js"
25+
}
26+
```
27+
28+
4. `npm link`
29+
30+
现在我们执行`npm link`启用命令行,现在再试试在终端直接输入`hi`命令,这次我们可以如愿见到结果
31+
32+
### 处理参数
33+
34+
命令行参数可通过系统变量`process.argv`获取。 *process.argv返回一个数组 第一个是node 第二个是脚本文件 第三个是输入的参数,*`process.argv[2]`开始得到才是真正的参数部分
35+
36+
```javascript
37+
#! /usr/bin/env node
38+
39+
let argv = process.argv.slice(2)
40+
let yourName = argv[0]
41+
console.log(`hi, ${yourName}!`)
42+
43+
// 执行 hi cosyer
44+
// hi, cosyer!
45+
```
46+
47+
### Commander.js
48+
对于参数处理,我们一般使用[commander](https://github.com/tj/commander.js),commander是一个轻巧的nodejs模块,提供了用户命令行输入和参数解析强大功能如:自记录代码、自动生成帮助、合并短参数(“ABC”==“-A-B-C”)、默认选项、强制选项、命令解析、提示符
49+
50+
```bash
51+
$ npm install commander --save
52+
```
53+
54+
```javascript
55+
#!/usr/bin/env node
56+
57+
/**
58+
* Module dependencies.
59+
*/
60+
61+
var program = require('commander')
62+
63+
program
64+
.version('0.0.1')
65+
.option('-p, --peppers', 'Add peppers')
66+
.option('-P, --pineapple', 'Add pineapple')
67+
.option('-b, --bbq-sauce', 'Add bbq sauce')
68+
.option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble')
69+
.parse(process.argv)
70+
71+
console.log('you ordered a pizza with:')
72+
if (program.peppers) console.log(' - peppers')
73+
if (program.pineapple) console.log(' - pineapple')
74+
if (program.bbqSauce) console.log(' - bbq')
75+
console.log(' - %s cheese', program.cheese)
76+
```
77+
### Commander API
78+
- `Option()`: 初始化自定义参数对象,设置“关键字”和“描述”
79+
- `Command()`: 初始化命令行参数对象,直接获得命令行输入
80+
- `Command#command()`: 定义一个命令名字
81+
- `Command#action()`: 注册一个callback函数
82+
- `Command#option()`: 定义参数,需要设置“关键字”和“描述”,关键字包括“简写”和“全写”两部分,以”,”,”|”,”空格”做分隔。
83+
- `Command#parse()`: 解析命令行参数argv
84+
- `Command#description()`: 设置description值
85+
- `Command#usage()`: 设置usage值
86+
- 更多参考 [commander官网](http://tj.github.io/commander.js/)
87+
88+
除了commander外,[yargs](http://yargs.js.org/)也是一个优秀的命令行参数处理模块
89+
90+
## 二.开发命令行翻译工具
91+
92+
### 1.新建并初始化项目
93+
新建 文件夹translator/进入目录下执行`npm init` 生成package.json文件
94+
```bash
95+
npm install commander superagent cli-table2 --save
96+
```
97+
- *[cli-table2](http://npm.taobao.org/package/cli-table2)命令行表格输出*
98+
- *[superagent](http://visionmedia.github.io/superagent/)用于http请求*
99+
100+
新建bin/translator.js文件,并加入package.json文件中
101+
```json
102+
"bin": {
103+
"translator": "bin/translator.js"
104+
},
105+
```
106+
然后
107+
```bash
108+
npm link
109+
```
110+
111+
这里我们会用到[有道API](http://fanyi.youdao.com/openapi?path=data-mode)
112+
一切准备就绪我们就可以进行编码了
113+
114+
### 2.coding
115+
*由于代码量很小,这里就直接贴代码,在代码中以注释讲解*
116+
```javascript
117+
#! /usr/bin/env node
118+
// 引入需要的模块
119+
const program = require('commander')
120+
const Table = require('cli-table2') // 表格输出
121+
const superagent = require('superagent') // http请求
122+
// 初始化commander
123+
program
124+
.allowUnknownOption()
125+
.version('0.0.1')
126+
.usage('translator <cmd> [input]')
127+
128+
// 有道api
129+
const API = 'http://fanyi.youdao.com/openapi.do?keyfrom=toaijf&key=868480929&type=data&doctype=json&version=1.1'
130+
131+
// 添加自定义命令
132+
program
133+
.command('query')
134+
.description('翻译输入')
135+
.action(function(word) {
136+
// 发起请求
137+
superagent.get(API)
138+
.query({ q: word})
139+
.end(function (err, res) {
140+
if(err){
141+
console.log('excuse me, try again')
142+
return false
143+
}
144+
let data = JSON.parse(res.text)
145+
let result = {}
146+
147+
// 返回的数据处理
148+
if(data.basic){
149+
result[word] = data['basic']['explains']
150+
}else if(data.translation){
151+
result[word] = data['translation']
152+
}else {
153+
console.error('error')
154+
}
155+
156+
// 输出表格
157+
let table = new Table()
158+
table.push(result)
159+
console.log(table.toString())
160+
})
161+
})
162+
163+
// 没有参数时显示帮助信息
164+
if (!process.argv[2]) {
165+
program.help();
166+
console.log();
167+
}
168+
169+
program.parse(process.argv)
170+
```
171+
172+
现在在终端中愉快的使用`translator`
173+
174+
```
175+
$ translator
176+
Usage: translator <cmd> [input]
177+
Commands:
178+
query 翻译输入
179+
Options:
180+
-h, --help output usage information
181+
-V, --version output the version number
182+
```
183+
184+
## 三.小结
185+
186+
1. 了解nodeJs 可执行脚本
187+
2. 了解命令行参数解析
188+
3. 了解commander,cli-table2,superagent等第三方模块
189+
190+
*抛砖引玉,更多请参考各个模块的官方示例及API文档*
191+
192+
### 相关链接
193+
- [CommandJs](http://tj.github.io/commander.js/#Command.prototype.parseExpectedArgs)
194+
- [cli-table2](http://npm.taobao.org/package/cli-table2)
195+
- [superagent](http://visionmedia.github.io/superagent/)
196+
- [Node.js 命令行程序开发教程-阮一峰](http://www.ruanyifeng.com/blog/2015/05/command-line-with-node.html)
197+
- [Commander写自己的Nodejs命令-粉丝日志](http://blog.fens.me/nodejs-commander/)

0 commit comments

Comments
 (0)