stdout,stdin, 和stderr 是标准的流,当程序执行时,它们在程序和环境之间互连输入和输出通信通道。
流一般意味着数据的流动。你可以把流想象成工厂里的传送带,连接到不同的机器(在我们的例子中是程序)。不同的机器可以被安排、指挥,并以某种方式与皮带(管道)连接,以产生特定的结果。
就像我们可以让物理I/O设备连接起来(通过鼠标输入,通过显示器输出),标准流对此进行了抽象,使我们的代码具有可组合性的力量。

就像我们可以用小命令组成强大的Linux命令一样,我们可以利用Node.js的标准流在Node.js中实现同样的功能。
Node.jsstdin,stdout, 和stdin
当我们运行一个Node.js程序时,会启动一个进程来执行该程序。
GNU文档将进程定义为 "分配系统资源的原始单位。每个进程都有自己的地址空间和(通常)一个控制线程。一个进程执行一个程序;你可以有多个进程执行同一个程序,但每个进程在自己的地址空间内有自己的程序副本,并独立于其他副本执行。"
每个进程初始化时都有三个开放的文件描述符,分别称为stdin 、stdout 、stderr 。
这三个文件描述符被统称为标准流。
为一个进程启动了一组三个标准流,我们可以通过Node.js中的process 对象访问它们。
标准流被视为有文件。访问任何文件的一个简单方法是使用与之相关的唯一的文件描述符。在这些标准流的情况下,有唯一的值分配给他们每个人。
process.stdin(0):标准输入流,它是程序的输入源。process.stdout(1):标准输出流,它是程序的输出源。process.stderr(2):标准错误流,用于由程序发出的错误信息和诊断。
简单使用stdin 和stdout
让我们写一个简单的应用程序,通过终端接收数据并将处理后的输出打印到终端。
我们会创建一个JavaScript文件(index.js),并编写下面的代码。
// index.js
process.stdin.on("data", data => {
data = data.toString().toUpperCase()
process.stdout.write(data + "\n")
})
运行上述程序会创建一个事件监听器来监听数据输入,处理输入,并将输出打印到终端。

我们可以在终端按ctrl + c ,停止运行的进程。
利用readline 来创建交互式终端脚本
readline 是一个Node.js模块,它提供了一个接口,用于一次从可读流(如 )中读取数据。process.stdin
首先,我们将创建一个名为index.js 的新的JavaScript文件,将readline 模块导入我们的程序,然后创建一个函数,ask ,接收一个字符串作为参数,并在我们的终端创建一个带有该字符串的提示。
// index.js
const readline = require("readline")
function ask(question) {
// asks a question and expect an answer
}
然后我们将使用readline 创建一个接口,将stdin 连接到stdout 。
// index.js
const readline = require("readline")
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
})
function ask(question) {
// asks a question and expectings an answer
}
我们将完成ask 函数以期待答案,并递归地重复整个过程。
// index.js
const readline = require("readline")
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
})
function ask(question) {
rl.question(question, (answer) => {
rl.write(`The answer received: ${answer}\n`)
ask(question)
})
}
ask("What is your name: ")
运行上述程序将创建一个终端界面,不断循环,直到我们在终端按下ctrl + c ,结束Node.js进程。
ctrl + c ,向我们正在运行的Node.js程序发送一个信号,称为SIGKILL ,它告诉Node.js停止我们的程序执行。我们也可以以编程方式,通过调用process.exit(exitCode) ,通知Node.js停止执行我们的应用程序。
因此,我们将更新我们的ask 函数,以检查是否来自输入 "q "的答案。如果输入的是 "q",那么它应该退出应用程序。
// index.js
const readline = require("readline")
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
})
function ask(question) {
rl.question(question, (answer) => {
if(answer === "q") {
process.exit(1)
}
rl.write(`The answer received: ${answer}\n`)
ask(question)
})
}
ask("What is your name: ")
什么是stderr ?
当我们编写应用程序或程序时,由于许多原因,可能会发生错误。stderr 是默认的文件描述符 进程可以在其中写入错误信息。
请看下面的代码。
// index.js
process.stderr.write("error! some error occurred\n")
用node index.js 来运行这个应用程序,会把错误信息写到我们的终端上,这与stdout 的输出方式相似。
我们很容易理解为什么会有stdin 和stdout 。然而,stderr 似乎很奇怪。
在基于UNIX/Linux的生态系统中,有一个时期,stderr 是不存在的。UNIX命令的所有输出都可以通过stdout ,包括命令的预期结果和错误或诊断信息。
这被认为不是一个最好的做法,因为错误也可以通过管道传递,而管道末端的命令可能无法理解。
因此,stderr ,通过一个不同的文件描述符来引导错误或诊断信息,这就是2 。
注意,在Linux中,当你用管道连接命令时,只有预期的结果被连接在一起。错误或诊断性错误信息是通过 stderr 文件描述符,并默认为打印到终端。
让我们通过编写两个名为logger.js 和printer.js 的Node.js程序来玩玩这个。
logger.js 是模拟一个日志程序,但在我们的案例中,日志已经被预定义了。
然后,printer.js 将从stdin 读取数据,并将它们写到一个文件中。
首先,我们将创建下面这个logger.js 。
const logObject = [
{
type: "normal",
message: "SUCCESS: 2 + 2 is 4"
},
{
type: "normal",
message: "SUCCESS 5 + 5 is 10"
},
{
type: "error",
message: "ERROR! 3 + 3 is not 4"
},
{
type: "normal",
message: "SUCESS 10 - 4 is 6"
}
]
function logger() {
logObject.forEach(log => {
setTimeout(() => {
if (log.type === "normal") process.stdout.write(log.message)
else process.stderr.write(log.message + '\n')
}, 500)
})
}
logger()
接下来,我们将创建另一个Node.js文件,创建或打开一个文本文件,logs.txt ,读取由stdout 提供的输入,并将它们写到文件中。
const fs = require("fs")
fs.open("./logs.txt", "w", (err, fd) => {
if (err) throw Error(err.message)
process.stdin.on("data", data => {
fs.write(fd, data.toString() + "\n", (err) => {
if (err) throw Error(err.message)
})
})
})
为了运行这个应用程序,我们可以在我们的终端中通过运行这两个程序进行管道连接。
$ node logger.js | node printer.js
注意,如果你在Windows下用Git Bash运行上述命令,你可能会遇到错误
stdout is not a tty。这可能是Git Bash的问题。你可以使用Window Powershell来运行该命令,或者在文件顶部加入shebang (#!/bin/env node)来使脚本可执行,并以./logger.js | ./printer.js的方式运行上述命令。
执行后,我们可以确认,只有通过stdout 的成功日志进入了logs.txt 。
// logs.txt
SUCCESS: 2 + 2 is 4
SUCCESS 5 + 5 is 10
SUCcESS 10 - 4 is 6
而错误日志则被打印到终端。这是stderr 的默认行为,但我们也可以通过重定向和管道来改变这种情况。
总结
现在我们明白了什么是标准流,以及我们如何在我们的Node.js应用程序中利用它们。我们还知道标准流如何帮助我们建立简单的程序,这些程序可以通过管道来制定更复杂的程序。
例如,printer.js ,不一定需要知道logger.js 做什么。printer.js 所做的就是接收来自stdout 的数据,并将日期写到一个文件中。
printer.js 可以与其他程序重复使用和组成,甚至与Linux命令一起使用,只要它们共享相同的执行环境。