Is there a way to erase the last line of output?
You can use the logUpdate
NPM module, or the vorpal
NPM module. These both do that.
The simple solution is to not print a newline character (i.e., do not use console.log
).
- Use
process.stdout.write
to print a line without the EOL character. - Use carriage return (
\r
) character to return to the begin of the line. - Use
\e[K
to clear all characters from the cursor position to the end of the line.
Example:
process.stdout.write("000");
process.stdout.write("\n111");
process.stdout.write("\n222");
To this line, the output will be:
000
111
222
However, if you execute:
process.stdout.write("000");
process.stdout.write("\n111");
process.stdout.write("\n222");
process.stdout.write("\r\x1b[K")
process.stdout.write("333");
The output is:
000
111
222\r\x1b[K
333
However, the terminal will display:
000
111
333
Way 1:
const clearLastLine = () => {
process.stdout.moveCursor(0, -1) // up one line
process.stdout.clearLine(1) // from cursor to end
}
Way 2:
const readline = require('readline')
const clearLastLine = () => {
readline.moveCursor(process.stdout, 0, -1) // up one line
readline.clearLine(process.stdout, 1) // from cursor to end
}
Way 3:
const ESC = '\x1b' // ASCII escape character
const CSI = ESC + '[' // control sequence introducer
const clearLastLine = () => {
process.stdout.write(CSI + 'A') // moves cursor up one line
process.stdout.write(CSI + 'K') // clears from cursor to line end
}
Way 1 uses the methods provided by the built-in tty
module.
Way 2 uses the built-in readline
module.
Way 3 uses ANSI escape sequences.
I learned about ANSI escape sequences by viewing the source code of log-update, which I found out about from dthree's answer. After doing some more research, I learned about the built-in readline
and tty
modules.
By reading the source code of Node.js, I learned that the methods used in way 1 are essentially wrappers around the methods used in way 2 and that the methods used in way 2 are essentially wrappers around the methods used in way 3. So every way ends up doing basically the same thing under the hood. I've ordered the ways from higher-level to lower-level.
I suggest using way 1 over way 2 and way 2 over way 3 mainly because I think that way 1 is less code and more clear (and therefore more readable) than way 2 and that way 2 is less code and more clear (and therefore more readable) than way 3 and that performance improvements (if any) achieved by using a lower-level way are likely negligible. However, I think way 3 avoids loading the built-in readline
module.
This solution (every way) assumes your cursor is at the start of an empty line right before your program calls clearLastLine()
. As Gajus states in his answer, the solution is simpler if instead your cursor is at the end of the last line. So could you use process.stdout.write()
(instead of console.log()
) to print the last line without a trailing newline?
Or the solution is even simpler if you don't need to print the last line in the first place. Is all this tinkering with stdout a code smell? Could you rearchitect your code so that the last line doesn't get printed at all?
In my case I'm testing a helper function I wrote that calls console.error()
, and I don't want the logged error message to show up in my test output. So instead of having my helper function call console.error()
, I could make it throw an error (or just return an error code and/or message or a boolean). And then my main program that calls my helper function could call console.error()
if my helper function throws an error (or returns an an error code and/or message or false
). Or I could pass a boolean flag argument to my helper function that tells it whether or not to log an error. Or I could redirect stderr, or mock the global console
or stub console.error()
, etc.
If you wanted to clear the last three lines written to system.stdout
, you could do:
const clearLastLines = (count) => {
process.stdout.moveCursor(0, -count)
process.stdout.clearScreenDown()
}
clearLastLines(3)
The following works for me:
process.stdout.clearLine();
process.stdout.cursorTo(0);