在Node.js中使用stdout、stdin和stderr的方法

6,122 阅读6分钟

stdout,stdin, 和stderr 是标准的流,当程序执行时,它们在程序和环境之间互连输入和输出通信通道。

流一般意味着数据的流动。你可以把流想象成工厂里的传送带,连接到不同的机器(在我们的例子中是程序)。不同的机器可以被安排、指挥,并以某种方式与皮带(管道)连接,以产生特定的结果。

就像我们可以让物理I/O设备连接起来(通过鼠标输入,通过显示器输出),标准流对此进行了抽象,使我们的代码具有可组合性的力量。

Testimage

就像我们可以用小命令组成强大的Linux命令一样,我们可以利用Node.js的标准流在Node.js中实现同样的功能。

Node.jsstdin,stdout, 和stdin

当我们运行一个Node.js程序时,会启动一个进程来执行该程序。

GNU文档将进程定义为 "分配系统资源的原始单位。每个进程都有自己的地址空间和(通常)一个控制线程。一个进程执行一个程序;你可以有多个进程执行同一个程序,但每个进程在自己的地址空间内有自己的程序副本,并独立于其他副本执行。"

每个进程初始化时都有三个开放的文件描述符,分别称为stdinstdoutstderr

这三个文件描述符被统称为标准流。

为一个进程启动了一组三个标准流,我们可以通过Node.js中的process 对象访问它们。

标准流被视为有文件。访问任何文件的一个简单方法是使用与之相关的唯一的文件描述符。在这些标准流的情况下,有唯一的值分配给他们每个人。

  • process.stdin(0):标准输入流,它是程序的输入源。
  • process.stdout(1):标准输出流,它是程序的输出源。
  • process.stderr(2):标准错误流,用于由程序发出的错误信息和诊断。

简单使用stdinstdout

让我们写一个简单的应用程序,通过终端接收数据并将处理后的输出打印到终端。

我们会创建一个JavaScript文件(index.js),并编写下面的代码。

 // index.js
process.stdin.on("data", data => {
    data = data.toString().toUpperCase()
    process.stdout.write(data + "\n")
})

运行上述程序会创建一个事件监听器来监听数据输入,处理输入,并将输出打印到终端。

Simple Result

我们可以在终端按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 的输出方式相似。

我们很容易理解为什么会有stdinstdout 。然而,stderr 似乎很奇怪。

在基于UNIX/Linux的生态系统中,有一个时期,stderr 是不存在的。UNIX命令的所有输出都可以通过stdout ,包括命令的预期结果和错误或诊断信息。

这被认为不是一个最好的做法,因为错误也可以通过管道传递,而管道末端的命令可能无法理解。

因此,stderr ,通过一个不同的文件描述符来引导错误或诊断信息,这就是2

注意,在Linux中,当你用管道连接命令时,只有预期的结果被连接在一起。错误或诊断性错误信息是通过 stderr 文件描述符,并默认为打印到终端。

让我们通过编写两个名为logger.jsprinter.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命令一起使用,只要它们共享相同的执行环境。