stdout
, stdin
, and stderr
are standard streams, which interconnect input and output communication channels between a program and its environment when the program executes.
Streams generally mean the flow of data. You can think of streams as a conveyor belt in a factory connected to different machines (programs in our case). Different machines can be arranged, directed, and connected with a belt (pipe) in a way to produce a particular result.
Just as we can have physical I/O devices connected (input via mouse, output via monitor), standard streams abstract this, giving us the power of composability in our code.
Just like the way we can compose powerful Linux commands from small commands, we can make use of Node.js standard streams to achieve the same in Node.js.
Node.js stdin
, stdout
, and stdin
When we run a Node.js program, a process is started for that program execution.
The GNU documentation defines a process as “the primitive units for allocation of system resources. Each process has its own address space and (usually) one thread of control. A process executes a program; you can have multiple processes executing the same program, but each process has its own copy of the program within its own address space and executes it independently of the other copies.”
Every process is initialized with three open file descriptors called stdin
, stdout
, and stderr
.
Those three file descriptors are collectively called the standard streams.
A set of the three standard streams is started for a process and we can access them via the process
object in Node.js.
The standards streams are treated as if there are files. An easy way to access any file is by using the unique file descriptor associated with it. In the case of these standards streams, there are unique values assigned to each of them.
process.stdin
(0): The standard input stream, which is a source of input for the programprocess.stdout
(1): The standard output stream, which is a source of output from the programprocess.stderr
(2): The standard error stream, which is used for error messages and diagnostics issued by the program
Simple use of stdin
and stdout
Let’s write a simple application that receives data via the terminal and prints a processed output into the terminal.
We’d create a JavaScript file (index.js
) and write the code below:
// index.js process.stdin.on("data", data => { data = data.toString().toUpperCase() process.stdout.write(data + "\n") })
Running the above program creates an event listener to listen for data input, processes the input, and prints the output to the terminal.
We can stop the running process in our terminal by pressing ctrl + c
.
Making use of readline
to create interactive terminal scripts
readline
is a Node.js module that provides an interface for reading data from a Readable stream (such as process.stdin
) one line at a time.
First, we would create a new JavaScript file called index.js
, import the readline
module into our program, then create a function, ask
, that receives a string as an argument and creates a prompt with the string in our terminal:
// index.js const readline = require("readline") function ask(question) { // asks a question and expect an answer }
Then we will create an interface using readline
that connects stdin
to 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 }
We will complete the ask
function to expect answers and repeat the whole process recursively:
// 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: ")
Running the above program would create a terminal interface that keeps looping until we end the Node.js process by pressing ctrl + c
in the terminal.
The ctrl + c
sends a signal to our running Node.js program called SIGKILL
, which tells Node.js to stop our program execution. We can also, programmatically, inform Node.js to stop executing our application by calling process.exit(exitCode)
.
So we will update our ask
function to check if the answer from input “q.” If the input is “q” then it should exit the application:
// 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: ")
What is stderr
?
When we write applications or programs, errors may occur due to so many reasons. stderr
is the default file descriptor where a process can write error messages.
Consider this code below:
// index.js process.stderr.write("error! some error occurred\n")
Running this application with node index.js
would write the error message to our terminal similarly to how stdout
would output it.
It is pretty straightforward to understand why stdin
and stdout
exist. However, stderr
seems pretty odd.
In the UNIX/Linux-based ecosystem, there was a period where stderr
did not exist. All outputs of UNIX commands could be piped via stdout
, including both expected results of the command and errors or diagnostic messages.
This is not considered a best practice as the error can also pass through the pipe which the command attached at the end of the pipe may not understand.
Hence, stderr
was created to direct error or diagnostic messages via a different file descriptor, which is 2
.
N.B., in Linux, when you pipe commands together, only the expecting result is piped together. The error or diagnostic error message is piped via the stderr
file descriptor and is by default printed to the terminal.
Let us play around with this by writing two Node.js programs called logger.js
and printer.js
.
The logger.js
is mocking a logging program, but in our case, the logs are predefined already.
Then the printer.js
would read data from the stdin
and write them to a file.
First, we will create the logger.js
below:
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()
Next, we will create another Node.js file that creates or opens a text file, logs.txt
, read inputs provided by stdout
, and writes them to a file:
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) }) }) })
To run this application, we can pipe these two programs in our terminal by running:
$ node logger.js | node printer.js
N.B., if you are running the above command with Git Bash in Windows, you may come across the error
stdout is not a tty
. This is likely an issue with Git Bash. You can run the command using Window Powershell or make the script executable by including a shebang (#!/bin/env node
) at the top of the file and running the command above as./logger.js | ./printer.js
.
After execution, we can confirm that only the success logs that passed by stdout
made it to the logs.txt
:
// logs.txt SUCCESS: 2 + 2 is 4 SUCCESS 5 + 5 is 10 SUCcESS 10 - 4 is 6
And that the error logs were printed to the terminal. This is the default behavior of stderr
but we can also change this through redirection and pipelines.
Wrapping up
Now we understand what the standard steams are and how we can make use of them in our Node.js application. We also know how standard streams can help us to build simple programs that can be channeled to formulate more complex programs.
For instance, printer.js
doesn’t necessarily need to be aware of what logger.js
does. All printer.js
do is receive data from a stdout
and write the date to a file.
printer.js
can be reused and composed with other programs, even with Linux commands, provided they share the same execution environment.
The post Using <code>stdout</code>, <code>stdin</code>, and <code>stderr</code> in Node.js appeared first on LogRocket Blog.
from LogRocket Blog https://ift.tt/iWmqb7P
via Read more