Node.js如何从标准输入读取按键输入

149

在运行中的Node.js脚本中,是否有可能监听输入的按键事件?如果我使用process.openStdin()并监听其'data'事件,那么输入会被缓存直到下一个换行符,就像这样:

// stdin_test.js
var stdin = process.openStdin();
stdin.on('data', function(chunk) { console.log("Got chunk: " + chunk); });

运行这个代码,我得到了:

$ node stdin_test.js
                <-- type '1'
                <-- type '2'
                <-- hit enter
Got chunk: 12

我想要看到的是:

$ node stdin_test.js
                <-- type '1' (without hitting enter yet)
 Got chunk: 1

我正在寻找nodejs的等效方法,例如在ruby中的getc函数。

这可能吗?


这是如何在输入中换行符发送之前逐个字符读取stdin的方法。 - dizzy
8个回答

184

对于那些在tty中功能被剥离的人,以下是如何从标准输入获取原始字符流的方法:

var stdin = process.stdin;

// without this, we would only get streams once enter is pressed
stdin.setRawMode( true );

// resume stdin in the parent process (node app won't quit all by itself
// unless an error or process.exit() happens)
stdin.resume();

// i don't want binary, do you?
stdin.setEncoding( 'utf8' );

// on any data into stdin
stdin.on( 'data', function( key ){
  // ctrl-c ( end of text )
  if ( key === '\u0003' ) {
    process.exit();
  }
  // write the key to stdout all normal like
  process.stdout.write( key );
});

这很简单 - 基本上就像process.stdin的文档一样,但使用setRawMode(true)获取原始流,这在文档中更难识别。


3
谢谢!这很简单,可以立即实现。 :) 正是我想要的。 - Kushal Likhi
2
不支持 Node.js 0.8+。您必须导入“keypress”。请参见 Peter Lyons 的答案。 - G-Wiz
2
这在0.8版本中确实有效,但有趣的是它是一个不断变化的API。 - Dan Heberden
1
如果你对那个条件有困难,我认为使用String(key)代替==。根据==的适当强制转换的想法可能会很麻烦。 - Dan Heberden
2
这是正确的答案,其他的已经不再适用了。 - Hola Soy Edu Feliz Navidad
显示剩余5条评论

70

在 Node.js 版本 >= v6.1.0 中:

const readline = require('readline');

readline.emitKeypressEvents(process.stdin);

if (process.stdin.setRawMode != null) {
  process.stdin.setRawMode(true);
}

process.stdin.on('keypress', (str, key) => {
  console.log(str)
  console.log(key)
})

请查看https://github.com/nodejs/node/issues/6626


3
在尝试在7上运行时,我收到了process.stdin.setRawMode is not a function的错误。稍后将尝试深入了解。 - Matt Molnar
3
如果是TTY,该函数才存在,因此首先要检查它是否为TTY。 - curiousdannii
1
@MattMolnar 你需要将你的应用程序作为外部终端运行,参见 https://dev59.com/FGQm5IYBdhLWcg3w0RxV#55893009 - Maksim Shamihulau
感谢@MaksimShamihulau提供的链接,让我找到了正确的方向。我在外部终端上运行,但同时也在使用nodemon。显然这也会影响TTY原始模式。 - Vlad Macovei

66
你可以这样实现,如果你切换到原始模式:
var stdin = process.openStdin(); 
require('tty').setRawMode(true);    

stdin.on('keypress', function (chunk, key) {
  process.stdout.write('Get Chunk: ' + chunk + '\n');
  if (key && key.ctrl && key.name == 'c') process.exit();
});

7
不用担心,我已经自己解决了。以下是代码:process.stdin.resume(); process.stdin.on('data', function (chunk) { process.stdout.write('data: ' + chunk); });这段代码用于从标准输入读取数据,并将数据写入标准输出。 - JamesM-SiteGen
3
将setRawMode移到openStdin()下方,因为只有在初始化stdin之后才能设置模式。 - Tower
4
似乎 stdin 不再触发一个 keypress 事件,而是会触发一个 data 事件,并且参数不同。 - skeggse
5
嗯,实际上stdin.on('keypress',function(chunk,key))在最新版本中已被移除。我很确定openStdin()要么已被移除,要么已被废弃。现在,您可以通过process.stdin访问标准输入。 - Lux
7
这个答案已经不再有用了,因为 Node.js API 有一些变化。请在下面找到正确的答案。 - Anton N
显示剩余5条评论

29

这个版本使用 keypress 模块,支持 node.js 版本 0.10、0.8 和 0.6,以及 iojs 2.3。请确保运行 npm install --save keypress

var keypress = require('keypress')
  , tty = require('tty');

// make `process.stdin` begin emitting "keypress" events
keypress(process.stdin);

// listen for the "keypress" event
process.stdin.on('keypress', function (ch, key) {
  console.log('got "keypress"', key);
  if (key && key.ctrl && key.name == 'c') {
    process.stdin.pause();
  }
});

if (typeof process.stdin.setRawMode == 'function') {
  process.stdin.setRawMode(true);
} else {
  tty.setRawMode(true);
}
process.stdin.resume();

这在 node v0.10.25 上不起作用,它说要使用 process.stdin.setRawMode() 代替,但是会出现错误并显示没有 setRawMode 方法,非常烦人。 - Plentybinary
1
@Plentybinary 我怀疑你实际上并没有运行 node v0.10.25。我已经在 v0.10.25 上测试过了,它可以正常工作,并且 process.stdin.setRawMode 存在、是一个函数,并且也可以正常工作。我还在 iojs-2.3.1 上进行了测试,它仍然可以正常工作。 - Peter Lyons
就我所知,这个功能至少在v0.10.40版本中仍然能够良好运行。 - John Rix

9

在测试了Node.js 0.6.4后(在版本0.8.14中测试失败):

rint = require('readline').createInterface( process.stdin, {} ); 
rint.input.on('keypress',function( char, key) {
    //console.log(key);
    if( key == undefined ) {
        process.stdout.write('{'+char+'}')
    } else {
        if( key.name == 'escape' ) {
            process.exit();
        }
        process.stdout.write('['+key.name+']');
    }

}); 
require('tty').setRawMode(true);
setTimeout(process.exit, 10000);

如果你运行它并且:

  <--type '1'
{1}
  <--type 'a'
{1}[a]

重要代码 #1:

require('tty').setRawMode( true );

重要代码 #2:

.createInterface( process.stdin, {} );

6
这将输出每个按键。用你喜欢的代码替换console.log即可。
process.stdin.setRawMode(true).setEncoding('utf8').resume().on('data',k=>console.log(k))

它完美地运行。如果您能在代码中添加一些关于其使用的注释,我将不胜感激。 - Manuel Rosendo Castro Iglesias
1
我发现StackOverflow上最有用的答案是那些简短的一行代码,可以快速复制粘贴而且不需要读很多解释。有时候我会找到自己以前写的非常简洁的答案,很喜欢把它们粘贴到我的新代码中。如果你理解JavaScript到一个很好的水平,那么代码的工作方式就非常明显了。如果你想要初学者级别的冗长注释代码,其他答案可能更适合你。对我来说,这些答案只是浪费时间,我只想要实质内容,而不是花里胡哨的东西。 - Jonathan
2
我的观点: 针对它的用途和操作进行调查,是浪费时间。 这真的是我需要的吗? 提前了解可以节省很多时间。 - Manuel Rosendo Castro Iglesias
process.stdin.setRawMode(true).resume().on('data',k=>console.log(k)) 是一个更短的版本,它返回一个缓冲区对象而不是字符字符串。 - Jonathan
请注意,如果您同时按下多个键,则会一次性在“k”中收到所有键代码。 - Jonathan

1
if(process.stdout.isTTY){
  process.stdin.on("readable",function(){
    var chunk = process.stdin.read();
    if(chunk != null) {
      doSomethingWithInput(chunk);
    }
  });
  process.stdin.setRawMode(true);
} else {
  console.log("You are not using a tty device...");
}

1

根据Dan Heberden的答案,这里是一个异步函数 -

async function getKeypress() {
  return new Promise(resolve => {
    var stdin = process.stdin
    stdin.setRawMode(true) // so get each keypress
    stdin.resume() // resume stdin in the parent process
    stdin.once('data', onData) // like on but removes listener also
    function onData(buffer) {
      stdin.setRawMode(false)
      resolve(buffer.toString())
    }
  })
}

使用方法如下 -
console.log("Press a key...")
const key = await getKeypress()
console.log(key)

1
两种可能,要么它不起作用,要么我不知道如何使用它。 - Manuel Rosendo Castro Iglesias
既然这是一个 Promise,那么它只能工作一次,对吗? - chamberlainpi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接