Wert interaktiv von der Konsole lesen


155

Ich dachte, einen einfachen Server-HTTP-Server mit einer Konsolenerweiterung zu erstellen. Ich habe das Snippet gefunden, das aus Befehlszeilendaten gelesen werden kann.

  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();

  });

Nun, um die Fragen wiederholt zu stellen, kann ich nicht einfach die while(done) { }Schleife verwenden? Auch gut, wenn der Server zur Fragestunde Ausgabe empfängt, ruiniert er die Leitung.


5
Ich nehme an, rldu meinst readline ?
Jpaugh

Sie können eine nicht blockierende Schnittstelle wie die in dieser Antwort verwendete verwenden und dann eine while(done)Schleife erstellen .
Keyvan

Antworten:


182

Sie können keine "while (done)" - Schleife ausführen, da dies eine Blockierung der Eingabe erfordern würde, was node.js nicht gerne tut.

Richten Sie stattdessen einen Rückruf ein, der bei jeder Eingabe aufgerufen wird:

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() + "]");
  });

2
Vielen Dank, dass dies funktioniert. Erlaubt der "End" -Listener, einige Schließvorgänge aufzurufen und "Auf Wiedersehen" zu sagen?
Risto Novik

Ich habe den "End" -Listener aus dem Beispiel entfernt. Ich weiß nicht, wo es wirklich nützlich sein wird, um ehrlich zu sein.
Rob

2
Sie können die Zeichenfolgenausgabe an d.toString () vereinfachen. Trim ()
MKN Web Solutions

6
Diese Antwort stammt aus dem Jahr 2011 und seitdem hat sich viel geändert. Insbesondere der allererste Teil der Antwort, dass Sie keine while-Schleife ausführen können, gilt nicht mehr. Ja, Sie können eine while-Schleife haben und dank des asynchronen Wartemusters immer noch nicht blockieren. Andere Antworten spiegeln dies wider. Wenn Sie dies heutzutage lesen, konsultieren Sie bitte auch andere Antworten.
Wiktor Zychla

1
Um @WiktorZychla zu folgen, war die Funktion process.openStdin, während sie noch arbeitete, um 2011 veraltet, und Sie werden keine Dokumentation dazu finden.
calder.ty

111

Ich habe eine andere API für diesen Zweck verwendet.

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);
});

Dies ermöglicht es, in einer Schleife aufzufordern, bis die Antwort lautet right. Es gibt auch eine nette kleine Konsole. Sie finden die Details unter http://nodejs.org/api/readline.html#readline_example_tiny_cli


11
Dies ist eine großartige Antwort. Was vielleicht nicht offensichtlich ist (aber ein großes Plus ist), ist, dass readline keine externe Abhängigkeit ist: Es ist Teil von node.js.
20.

51

Die Readline-API hat sich seit 12 'ziemlich verändert. Die Dokumente zeigen ein nützliches Beispiel zum Erfassen von Benutzereingaben aus einem Standard-Stream:

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();
});

Weitere Informationen hier.


5
Dies ist nur ein einfaches Beispiel. Wie interagierst du? Frage Antwort? Mehrfachauswahl und dergleichen? Wie man rl nach dem Schließen wieder öffnet, wenn man nicht mit open rl arbeiten kann, um mit dem Benutzer zu interagieren, einschließlich einiger Logik
Pawel Cioch

27

Ich glaube, dies verdient eine moderne async-awaitAntwort, vorausgesetzt, es wird der Knoten> = 7.x verwendet.

Die Antwort verwendet ReadLine::questionsie immer noch , umschließt sie jedoch so, dass dies while (done) {}möglich ist, worüber das OP explizit fragt.

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

und dann eine beispielhafte Verwendung

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

führt zu folgendem Gespräch

Are you sure? no
Are you sure? no
Are you sure? yes
finally you are sure!

Dies ist genau die Antwort, nach der ich gesucht habe. Ich denke, es sollte das Top sein.
William Chou

Wunderschönen. Für größere Skripte ist ein asynchrones Warten erforderlich. Genau das brauchte ich.
Abhay Shiro

25

Bitte verwenden Sie readline-sync , damit Sie mit der synchronen Konsole ohne Rückruf-Hells arbeiten können. Funktioniert sogar mit Passwörtern:

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


5
Dies erfordert zusätzliche Abhängigkeit, daher würde ich andere Lösungen bevorzugen.
Risto Novik

Läuft nicht auf SO "Ungefangener Referenzfehler: Lesen ist nicht definiert"
awwsmm

12

Die Antwort von @rob funktioniert meistens, funktioniert jedoch möglicherweise nicht wie erwartet bei langen Eingaben.

Das sollten Sie stattdessen verwenden:

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

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

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

Erklärung, warum diese Lösung funktioniert:

addListener('data') funktioniert wie ein Puffer, Rückruf wird aufgerufen, wenn er voll ist oder / und das Ende der Eingabe ist.

Was ist mit langen Eingaben? Ein einzelnes'data' Rückruf reicht nicht aus, daher wird Ihre Eingabe in zwei oder mehr Teile aufgeteilt. Das ist oft nicht bequem.

addListener('end')wird uns benachrichtigen, wenn der Standardleser mit dem Lesen unserer Eingaben fertig ist. Da wir die vorherigen Daten gespeichert haben, können wir sie jetzt alle zusammen lesen und verarbeiten.


3
Wenn ich den obigen Code verwende und eine Eingabe eingebe und dann die Eingabetaste drücke, bittet mich die Konsole immer wieder um weitere Eingabe. Wie sollen wir es beenden?
Matan Tubul

5

Ich empfehle die Verwendung von Inquirer , da es eine Sammlung gängiger interaktiver Befehlszeilenbenutzeroberflächen bietet.

const inquirer = require('inquirer');

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

const answers = await inquirer.prompt(questions);
console.log(answers);

5

Hier ist ein Beispiel:

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
})

Ausgabe:

Enter name: bob
Your name is: bob

Schöne Antwort Bruder !! Einfach und klar.
MD.JULHAS HOSSAIN

3

Dies ist zu kompliziert. Eine einfachere Version von:

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

wäre zu verwenden

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

dann wird es warten, wenn Sie verwenden

rl.question('string');

dann ist es einfacher zu wiederholen. beispielsweise:

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!');
}

2

Ein häufiger Anwendungsfall wäre wahrscheinlich, dass die App eine generische Eingabeaufforderung anzeigt und diese in einer switch-Anweisung behandelt.

Sie könnten ein Verhalten erhalten, das einer while-Schleife entspricht, indem Sie eine Hilfsfunktion verwenden, die sich im Rückruf selbst aufruft:

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;
    }
});

Sie können eine leere Zeichenfolge übergeben, anstatt 'app> 'dass Ihre App bereits etwas außerhalb dieser Schleife auf den Bildschirm druckt.


2

Mein Ansatz hierfür wäre die Verwendung von asynchronen Generatoren .

Angenommen, Sie haben eine Reihe von Fragen:

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

Um das awaitSchlüsselwort verwenden zu können, müssen Sie Ihr Programm in ein asynchrones IIFE einbinden.

(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)
})();

Erwartete Ergebnisse:

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' ]

Wenn Sie Fragen und Antworten erhalten möchten, können Sie dies mit einer einfachen Änderung erreichen:

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' ] ]
   */

2

Ich musste ein "Tic-Tac-Toe" -Spiel in Node schreiben, das Eingaben über die Befehlszeile entgegennahm, und diesen grundlegenden asynchronen / abwartenden Codeblock schreiben, der den Trick ausführte.

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()

1

Blockieren des nicht blockierten Readline-Verhaltens

Stellen Sie sich vor, Sie müssen drei Fragen von der Konsole aus beantworten, da Sie jetzt wissen, dass dieser Code nicht ausgeführt wird, da das Readline-Standardmodul das Verhalten "nicht blockiert" aufweist. Angenommen, jede rl.question ist ein unabhängiger Thread, sodass dieser Code nicht ausgeführt wird.

'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 );

Laufende Ausgabe:

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)

Die vorgeschlagene Lösung verwendet einen Ereignisemitter, um das Ende eines nicht blockierenden Threads zu signalisieren, und bezieht die Schleifenlogik und das Programmende in ihre Listener-Funktion ein.

'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

Laufende Ausgabe:

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' ] ]

0

Ich habe ein kleines Skript für das Leseverzeichnis erstellt und eine neue Datei mit Konsolennamen (Beispiel: 'name.txt') und Text in eine Datei geschrieben.

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();
  }
});

0

Am einfachsten ist es, Readline-Sync zu verwenden

Es verarbeitet eine Eingabe und Ausgabe nach der anderen.

npm i readline-sync

z.B:

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)
}

Sie sollten wirklich Ihre requireAussage hinzufügen. Es gibt keinen Grund, es wegzulassen.
Solidstatejake
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.