Lectura de valor desde la consola, interactivamente

155

Pensé hacer un servidor http simple con alguna extensión de consola. Encontré el fragmento para leer de los datos de la línea de comandos.

  var i = rl.createInterface(process.stdin, process.stdout, null);
  i.question('Write your name: ', function(answer) {
    console.log('Nice to meet you> ' + answer);
    i.close();
    process.stdin.destroy();

  });

bueno para hacer las preguntas repetidamente, simplemente no puedo usar el while(done) { }bucle? También bien si el servidor recibe salida en el momento de la pregunta, arruina la línea.

Risto Novik
fuente
55
Supongo que rlte refieres a readline ?
jpaugh
Puede usar una interfaz sin bloqueo como la que se usa en esta respuesta , luego puede hacer un while(done)bucle.
Keyvan

Respuestas:

182

no puede hacer un ciclo "while (done)" porque eso requeriría bloqueo en la entrada, algo que a node.js no le gusta hacer.

En su lugar, configure una devolución de llamada para que se llame cada vez que ingrese algo:

var stdin = process.openStdin();

stdin.addListener("data", function(d) {
    // note:  d is an object, and when converted to a string it will
    // end with a linefeed.  so we (rather crudely) account for that  
    // with toString() and then trim() 
    console.log("you entered: [" + 
        d.toString().trim() + "]");
  });
robar
fuente
2
Gracias, esto funciona, ¿el oyente "final" permite llamar a algunas operaciones de cierre y decir 'Adiós'?
Risto Novik
Quité el oyente "final" del ejemplo, no sé dónde será realmente útil para ser honesto.
robar
2
Puede simplificar la salida de cadena a d.toString (). Trim ()
MKN Web Solutions
66
Esta respuesta data de 2011 y mucho ha cambiado desde entonces. En particular, la primera parte de la respuesta, no puedes hacer un ciclo while ... ya no se sostiene. Sí, puede tener un ciclo while y aún no bloquear, gracias al patrón de espera asíncrona. Otras respuestas reflejan eso. Para cualquiera que lea esto hoy en día, consulte otras respuestas también.
Wiktor Zychla
1
Para seguir en @WiktorZychla, la función process.openStdin mientras aún funcionaba, quedó en desuso alrededor de 2011, y no encontrará ninguna documentación al respecto.
calder.ty
112

He usado otra API para este propósito ...

var readline = require('readline');
var rl = readline.createInterface(process.stdin, process.stdout);
rl.setPrompt('guess> ');
rl.prompt();
rl.on('line', function(line) {
    if (line === "right") rl.close();
    rl.prompt();
}).on('close',function(){
    process.exit(0);
});

Esto permite solicitar en bucle hasta que la respuesta sea right. También ofrece una pequeña consola agradable. Puede encontrar los detalles en http://nodejs.org/api/readline.html#readline_example_tiny_cli

Madhan Ganesh
fuente
11
Esta es una respuesta genial. Lo que podría no ser obvio (pero es una gran ventaja) es que readline no es una dependencia externa: es parte de node.js.
jlh
51

La API de Readline ha cambiado bastante desde 12 '. Los documentos muestran un ejemplo útil para capturar la entrada del usuario de una secuencia estándar:

const readline = require('readline');

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

rl.question('What do you think of Node.js? ', (answer) => {
  console.log('Thank you for your valuable feedback:', answer);
  rl.close();
});

Más información aquí.

Patrick.SE
fuente
55
Este es solo un ejemplo básico. ¿Cómo interactúas? ¿pregunta respuesta? múltiples opciones y similares? Cómo volver a abrir rl una vez cerrado, si no se puede trabajar con open rl para interactuar con el usuario, incluida cierta lógica
Pawel Cioch
28

Creo que esto merece una async-awaitrespuesta moderna , suponiendo que se use el nodo> = 7.x.

La respuesta todavía la usa, ReadLine::questionpero la envuelve para que while (done) {}sea ​​posible, que es algo que el OP pregunta explícitamente.

var cl = readln.createInterface( process.stdin, process.stdout );
var question = function(q) {
    return new Promise( (res, rej) => {
        cl.question( q, answer => {
            res(answer);
        })
    });
};

y luego un ejemplo de uso

(async function main() {
    var answer;
    while ( answer != 'yes' ) {
        answer = await question('Are you sure? ');
    }
    console.log( 'finally you are sure!');
})();

conduce a la siguiente conversación

Are you sure? no
Are you sure? no
Are you sure? yes
finally you are sure!
Wiktor Zychla
fuente
Esta es exactamente la respuesta que estaba buscando. Creo que debería ser el mejor.
William Chou
Hermoso. La espera asíncrona es necesaria para scripts más grandes. Esto es exactamente lo que necesitaba.
Abhay Shiro
25

Utilice readline-sync , esto le permite trabajar con una consola síncrona sin infusiones de devoluciones de llamada. Incluso funciona con contraseñas:

var favFood = read.question('What is your favorite food? ', {
  hideEchoBack: true // The typed text on screen is hidden by `*` (default). 
});

Arango
fuente
55
Esto requiere una dependencia adicional, por lo que preferiría otras soluciones.
Risto Novik
No se ejecuta en SO "Error de referencia no
capturado
12

La respuesta @rob funcionará la mayoría de las veces, pero podría no funcionar como se espera con entradas largas.

Eso es lo que deberías usar en su lugar:

const stdin = process.openStdin();
let content = '';

stdin.addListener('data', d => {
  content += d.toString();
});

stdin.addListener('end', () => {
  console.info(`Input: ${content}`);
});

Explicación sobre por qué funciona esta solución:

addListener('data') funciona como un búfer, se devolverá la llamada cuando esté lleno o / y sea el final de la entrada.

¿Qué pasa con las entradas largas? Una sola 'data'devolución de llamada no será suficiente, por lo tanto, obtendrá su entrada dividida en dos o más partes. Eso a menudo no es conveniente.

addListener('end')nos notificará cuando el lector de stdin termine de leer nuestra entrada. Como hemos estado almacenando los datos anteriores, ahora podemos leerlos y procesarlos todos juntos.

zurfyx
fuente
3
cuando estoy usando el código anterior e inserto alguna entrada y luego la tecla "enter" la consola sigue pidiéndome más información. ¿Cómo deberíamos terminarlo?
Matan Tubul
5

Recomiendo usar Inquirer , ya que proporciona una colección de interfaces de usuario de línea de comandos interactivas comunes.

const inquirer = require('inquirer');

const questions = [{
  type: 'input',
  name: 'name',
  message: "What's your name?",
}];

const answers = await inquirer.prompt(questions);
console.log(answers);
Diogo Cardoso
fuente
5

Aquí hay un ejemplo:

const stdin = process.openStdin()

process.stdout.write('Enter name: ')

stdin.addListener('data', text => {
  const name = text.toString().trim()
  console.log('Your name is: ' + name)

  stdin.pause() // stop reading
})

Salida:

Enter name: bob
Your name is: bob
Miguel Mota
fuente
Buena respuesta hermano !! Simplemente simple y claro.
MD.JULHAS HOSSAIN
3

Esto es demasiado complicado. Una versión más fácil de:

var rl = require('readline');
rl.createInterface... etc

sería usar

var rl = require('readline-sync');

entonces esperará cuando uses

rl.question('string');

entonces es más fácil repetir. por ejemplo:

var rl = require('readline-sync');
for(let i=0;i<10;i++) {
    var ans = rl.question('What\'s your favourite food?');
    console.log('I like '+ans+' too!');
}
Ragnarok Ragdoll
fuente
2

Un caso de uso común probablemente sería que la aplicación muestre un mensaje genérico y lo maneje en una declaración de cambio.

Podría obtener un comportamiento equivalente a un ciclo while utilizando una función auxiliar que se llamaría a sí misma en la devolución de llamada:

const readline = require('readline');
const rl = readline.createInterface(process.stdin, process.stdout);

function promptInput (prompt, handler)
{
    rl.question(prompt, input =>
    {
        if (handler(input) !== false)
        {
            promptInput(prompt, handler);
        }
        else
        {
            rl.close();
        }
    });
}

promptInput('app> ', input =>
{
    switch (input)
    {
        case 'my command':
            // handle this command
            break;
        case 'exit':
            console.log('Bye!');
            return false;
    }
});

Puede pasar una cadena vacía en lugar de 'app> 'si su aplicación ya imprime algo en la pantalla fuera de este bucle.

zoran404
fuente
2

Mi enfoque para esto sería usar generadores asíncronos .

Suponiendo que tiene una serie de preguntas:

 const questions = [
        "How are you today ?",
        "What are you working on ?",
        "What do you think of async generators ?",
    ]

Para utilizar la awaitpalabra clave, debe envolver su programa en un IIFE asíncrono.

(async () => {

    questions[Symbol.asyncIterator] = async function * () {
        const stdin = process.openStdin()

        for (const q of this) {
            // The promise won't be solved until you type something
            const res = await new Promise((resolve, reject) => {
                console.log(q)

                stdin.addListener('data', data => {
                    resolve(data.toString())
                    reject('err')
                });
            })

            yield [q, res];
        }

    };

    for await (const res of questions) {
        console.log(res)
    }

    process.exit(0)
})();

Resultados previstos:

How are you today ?
good
[ 'How are you today ?', 'good\n' ]
What are you working on ?
:)
[ 'What are you working on ?', ':)\n' ]
What do you think about async generators ?
awesome
[ 'What do you think about async generators ?', 'awesome\n' ]

Si desea obtener preguntas y respuestas por completo, puede lograr esto con una simple modificación:

const questionsAndAnswers = [];

    for await (const res of questions) {
        // console.log(res)
        questionsAndAnswers.push(res)
    }

    console.log(questionsAndAnswers)

   /*
     [ [ 'How are you today ?', 'good\n' ],
     [ 'What are you working on ?', ':)\n' ],
     [ 'What do you think about async generators ?', 'awesome\n' ] ]
   */
Andrei Gătej
fuente
2

Tuve que escribir un juego de "tic-tac-toe" en Node que tomaba datos de la línea de comando y escribía este bloque básico de código asíncrono / espera que hizo el truco.

const readline = require('readline')

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

async function getAnswer (prompt) {
  const answer = await new Promise((resolve, reject) =>{
    rl.question(`${prompt}\n`, (answer) => {
      resolve(answer)
    });
  })
  return answer
}

let done = false
const playGame = async () => {
  let i = 1
  let prompt = `Question #${i}, enter "q" to quit`
  while (!done) {
    i += 1
    const answer = await getAnswer(prompt)
    console.log(`${answer}`)
    prompt = processAnswer(answer, i)
  }
  rl.close()
}

const processAnswer = (answer, i) => {
  // this will be set depending on the answer
  let prompt = `Question #${i}, enter "q" to quit`
  // if answer === 'q', then quit
  if (answer === 'q') {
    console.log('User entered q to quit')
    done = true
    return
  }
  // parse answer

  // if answer is invalid, return new prompt to reenter

  // if answer is valid, process next move

  // create next prompt
  return prompt
}

playGame()
Stefan Musarra
fuente
1

Bloqueo de comportamiento de desbloqueo de línea de lectura

Imagine que tiene tres preguntas que responder desde la consola, ya que ahora sabe que este código no se ejecutará porque el módulo estándar readline tiene un comportamiento 'desbloqueado' que dice que cada pregunta rl es un hilo independiente, por lo que este código no se ejecutará.

'use strict';

var questionaire=[['First Question: ',''],['Second Question: ',''],['Third Question: ','']];

function askaquestion(question) {
const readline = require('readline');

const rl = readline.createInterface(
    {input: process.stdin, output:process.stdout}
    );
  rl.question(question[0], function(answer) {
    console.log(answer);
    question[1] = answer;
    rl.close();
  });
};

var i=0;  
for (i=0; i < questionaire.length; i++) {
askaquestion(questionaire[i]);
}

console.log('Results:',questionaire );

Ejecución de salida:

node test.js
Third Question: Results: [ [ 'First Question: ', '' ],
  [ 'Second Question: ', '' ],
  [ 'Third Question: ', '' ] ]        <--- the last question remain unoverwritten and then the final line of the program is shown as the threads were running waiting for answers (see below)
aaa        <--- I responded with a single 'a' that was sweeped by 3 running threads
a        <--- Response of one thread

a        <--- Response of another thread

a        <--- Response of another thread (there is no order on threads exit)

La solución propuesta utiliza un emisor de eventos para señalar el final de un hilo de desbloqueo e incluye la lógica del bucle y el final del programa en su función de escucha.

'use strict';

var questionaire=[['First Question: ',''],['Second Question: ',''],['Third Question: ','']];

// Introduce EventEmitter object
const EventEmitter = require('events');

class MyEmitter extends EventEmitter {};

const myEmitter = new MyEmitter();
myEmitter.on('continue', () => {
  console.log('continue...');
  i++; if (i< questionaire.length) askaquestion(questionaire[i],myEmitter);    // add here relevant loop logic
           else console.log('end of loop!\nResults:',questionaire );
});
//

function askaquestion(p_question,p_my_Emitter) { // add a parameter to include my_Emitter
const readline = require('readline');

const rl = readline.createInterface(
    {input: process.stdin, output:process.stdout}
    );
  rl.question(p_question[0], function(answer) {
    console.log(answer);
    p_question[1] = answer;
    rl.close();
    myEmitter.emit('continue');    // Emit 'continue' event after the question was responded (detect end of unblocking thread)
  });
};

/*var i=0;  
for (i=0; i < questionaire.length; i++) {
askaquestion(questionaire[i],myEmitter);
}*/

var i=0;
askaquestion(questionaire[0],myEmitter);        // entry point to the blocking loop


// console.log('Results:',questionaire )    <- moved to the truly end of the program

Ejecución de salida:

node test2.js
First Question: 1
1
continue...
Second Question: 2
2
continue...
Third Question: 3
3
continue...
done!
Results: [ [ 'First Question: ', '1' ],
  [ 'Second Question: ', '2' ],
  [ 'Third Question: ', '3' ] ]
vlc33
fuente
0

He creado un pequeño script para leer el directorio y escribir un nuevo archivo de nombre de consola (ejemplo: 'nombre.txt') y texto en el archivo.

const readline = require('readline');
const fs = require('fs');

const pathFile = fs.readdirSync('.');

const file = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

file.question('Insert name of your file? ', (f) => {
  console.log('File is: ',f.toString().trim());
  try{
    file.question('Insert text of your file? ', (d) => {
      console.log('Text is: ',d.toString().trim());
      try {
        if(f != ''){
          if (fs.existsSync(f)) {
            //file exists
            console.log('file exist');
            return file.close();
          }else{
            //save file
            fs.writeFile(f, d, (err) => {
                if (err) throw err;
                console.log('The file has been saved!');
                file.close();
            });
          }
        }else{
          //file empty 
          console.log('Not file is created!');
          console.log(pathFile);
          file.close();
        }
      } catch(err) {
        console.error(err);
        file.close();
      }
    });
  }catch(err){
    console.log(err);
    file.close();
  }
});
niksolaz
fuente
0

La forma más fácil es usar readline-sync

Procesa una por una entrada y sale.

npm i readline-sync

p.ej:

var firstPrompt = readlineSync.question('Are you sure want to initialize new db? This will drop whole database and create new one, Enter: (yes/no) ');

if (firstPrompt === 'yes') {
    console.log('--firstPrompt--', firstPrompt)
    startProcess()
} else if (firstPrompt === 'no') {
    var secondPrompt = readlineSync.question('Do you want to modify migration?, Enter: (yes/no) ');
    console.log('secondPrompt ', secondPrompt)
    startAnother()
} else {
    console.log('Invalid Input')
    process.exit(0)
}
Rohit Parte
fuente
Realmente deberías incluir tu requiredeclaración. No hay razón para dejarlo fuera.
solidstatejake